@webiny/app-headless-cms 6.5.0-beta.11 → 6.5.0-beta.12
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/admin/components/ContentEntries/Filters/RefFieldRenderer.js +1 -0
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.js.map +1 -1
- package/admin/plugins/editor/defaultBar/Name/Name.d.ts +0 -5
- package/admin/plugins/editor/defaultBar/Name/Name.js +0 -2
- package/admin/plugins/editor/defaultBar/Name/Name.js.map +1 -1
- package/features/contentEntry/feature.js +2 -0
- package/features/contentEntry/feature.js.map +1 -1
- package/features/contentEntry/referenceTitles/ReferenceTitles.d.ts +18 -0
- package/features/contentEntry/referenceTitles/ReferenceTitles.js +58 -0
- package/features/contentEntry/referenceTitles/ReferenceTitles.js.map +1 -0
- package/features/contentEntry/referenceTitles/ReferenceTitles.test.d.ts +1 -0
- package/features/contentEntry/referenceTitles/ReferenceTitles.test.js +173 -0
- package/features/contentEntry/referenceTitles/ReferenceTitles.test.js.map +1 -0
- package/features/contentEntry/referenceTitles/RememberTitlesDecorators.d.ts +22 -0
- package/features/contentEntry/referenceTitles/RememberTitlesDecorators.js +40 -0
- package/features/contentEntry/referenceTitles/RememberTitlesDecorators.js.map +1 -0
- package/features/contentEntry/referenceTitles/abstractions.d.ts +16 -0
- package/features/contentEntry/referenceTitles/abstractions.js +5 -0
- package/features/contentEntry/referenceTitles/abstractions.js.map +1 -0
- package/features/contentEntry/referenceTitles/feature.d.ts +3 -0
- package/features/contentEntry/referenceTitles/feature.js +20 -0
- package/features/contentEntry/referenceTitles/feature.js.map +1 -0
- package/features/formModel/CmsRefFieldValueFormatter.d.ts +12 -0
- package/features/formModel/CmsRefFieldValueFormatter.js +29 -0
- package/features/formModel/CmsRefFieldValueFormatter.js.map +1 -0
- package/features/formModel/feature.js +2 -0
- package/features/formModel/feature.js.map +1 -1
- package/package.json +20 -20
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js +1 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js.map +1 -1
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js +34 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js.map +1 -1
- package/presentation/contentEntries/views/ContentEntryFormView.js +1 -0
- package/presentation/contentEntries/views/ContentEntryFormView.js.map +1 -1
- package/presentation/fieldRenderers/ref/CmsRefInputRenderer.js +1 -0
- package/presentation/fieldRenderers/ref/CmsRefInputRenderer.js.map +1 -1
- package/presentation/fieldRenderers/ref/CmsRefInputsRenderer.js +1 -0
- package/presentation/fieldRenderers/ref/CmsRefInputsRenderer.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin/components/ContentEntries/Filters/RefFieldRenderer.js","sources":["../../../../../src/admin/components/ContentEntries/Filters/RefFieldRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Bind } from \"@webiny/form\";\nimport type { BindComponentRenderProp } from \"@webiny/form\";\nimport { AutoComplete } from \"@webiny/admin-ui\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { ContentEntryListConfig } from \"~/admin/config/contentEntries/index.js\";\nimport { RefSingleAutocompletePresenterFeature } from \"~/presentation/fieldRenderers/ref/autocomplete/single/feature.js\";\n\nconst { useInputField } = ContentEntryListConfig.Browser.AdvancedSearch.FieldRenderer;\n\n/**\n * The advanced search filter stores the selected reference as a JSON string\n * `{ entryId, modelId }`. `GraphQLInputMapper` reads `entryId` from it to build the\n * `<fieldId>.entryId` where condition, and `modelId` is used to load the label back\n * when a saved filter is opened.\n */\ninterface RefFilterValue {\n entryId: string;\n modelId: string;\n}\n\nconst parseValue = (value: unknown): RefFilterValue | null => {\n if (typeof value !== \"string\" || !value) {\n return null;\n }\n try {\n const parsed = JSON.parse(value);\n if (!parsed?.entryId || !parsed?.modelId) {\n return null;\n }\n return { entryId: parsed.entryId, modelId: parsed.modelId };\n } catch {\n return null;\n }\n};\n\nexport const Ref = () => {\n const { name, field } = useInputField();\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefSingleAutocompletePresenterFeature.register(child);\n return child;\n }, [field.value]);\n\n return (\n <DiContainerProvider container={scopedContainer} key={field.value}>\n <Bind name={name}>\n {bind => <RefInner bind={bind} modelIds={field.settings.modelIds} />}\n </Bind>\n </DiContainerProvider>\n );\n};\n\ninterface RefInnerProps {\n bind: BindComponentRenderProp;\n modelIds: string[];\n}\n\nconst RefInner = observer(({ bind, modelIds }: RefInnerProps) => {\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n useEffect(() => {\n const value = parseValue(bind.value);\n presenter.init({\n modelIds,\n value: value ? { id: value.entryId, modelId: value.modelId } : null\n });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={\"Value\"}\n size={\"lg\"}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n validation={bind.validation}\n onValueSearch={query => presenter.search(query)}\n onValueChange={entryId => {\n if (!entryId) {\n presenter.clear();\n bind.onChange(\"\");\n return;\n }\n const ref = presenter.select(entryId);\n if (ref) {\n bind.onChange(JSON.stringify({ entryId, modelId: ref.modelId }));\n }\n }}\n onValueReset={() => {\n presenter.clear();\n bind.onChange(\"\");\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n"],"names":["useInputField","ContentEntryListConfig","parseValue","value","parsed","JSON","Ref","name","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefSingleAutocompletePresenterFeature","DiContainerProvider","Bind","bind","RefInner","observer","modelIds","presenter","useFeature","useEffect","vm","AutoComplete","query","entryId","ref"],"mappings":";;;;;;;AASA,MAAM,EAAEA,eAAAA,aAAa,EAAE,GAAGC,uBAAuB,OAAO,CAAC,cAAc,CAAC,aAAa;AAarF,MAAMC,aAAa,CAACC;IAChB,IAAI,AAAiB,YAAjB,OAAOA,SAAsB,CAACA,OAC9B,OAAO;IAEX,IAAI;QACA,MAAMC,SAASC,KAAK,KAAK,CAACF;QAC1B,IAAI,CAACC,QAAQ,WAAW,CAACA,QAAQ,SAC7B,OAAO;QAEX,OAAO;YAAE,SAASA,OAAO,OAAO;YAAE,SAASA,OAAO,OAAO;QAAC;IAC9D,EAAE,OAAM;QACJ,OAAO;IACX;AACJ;AAEO,MAAME,MAAM;IACf,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,GAAGR;IACxB,MAAMS,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,sCAAsC,QAAQ,CAACD;QAC/C,OAAOA;IACX,GAAG;QAACL,MAAM,KAAK;KAAC;IAEhB,OAAO,WAAP,GACI,oBAACO,qBAAmBA;QAAC,WAAWJ;QAAiB,KAAKH,MAAM,KAAK;qBAC7D,oBAACQ,MAAIA;QAAC,MAAMT;OACPU,CAAAA,OAAAA,WAAAA,GAAQ,oBAACC,UAAQA;YAAC,MAAMD;YAAM,UAAUT,MAAM,QAAQ,CAAC,QAAQ;;AAIhF;AAOA,MAAMU,WAAWC,SAAS,CAAC,EAAEF,IAAI,EAAEG,QAAQ,EAAiB;IACxD,MAAM,EAAEC,SAAS,EAAE,GAAGC,WAAWR;IAEjCS,UAAU;QACN,MAAMpB,QAAQD,WAAWe,KAAK,KAAK;QACnCI,UAAU,IAAI,CAAC;YACXD;YACA,OAAOjB,QAAQ;gBAAE,IAAIA,MAAM,OAAO;gBAAE,SAASA,MAAM,OAAO;YAAC,IAAI;QACnE;IACJ,GAAG,EAAE;IAEL,MAAMqB,KAAKH,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACI,cAAYA;QACT,OAAO;QACP,MAAM;QACN,SAASD,GAAG,OAAO;QACnB,OAAOA,GAAG,KAAK;QACf,SAASA,GAAG,OAAO;QACnB,YAAYP,KAAK,UAAU;QAC3B,eAAeS,CAAAA,QAASL,UAAU,MAAM,CAACK;QACzC,eAAeC,CAAAA;YACX,IAAI,CAACA,SAAS;gBACVN,UAAU,KAAK;gBACfJ,KAAK,QAAQ,CAAC;gBACd;YACJ;YACA,MAAMW,MAAMP,UAAU,MAAM,CAACM;YAC7B,IAAIC,KACAX,KAAK,QAAQ,CAACZ,KAAK,SAAS,CAAC;gBAAEsB;gBAAS,SAASC,IAAI,OAAO;YAAC;QAErE;QACA,cAAc;YACVP,UAAU,KAAK;YACfJ,KAAK,QAAQ,CAAC;QAClB;QACA,oBAAoBO,GAAG,QAAQ;;AAG3C"}
|
|
1
|
+
{"version":3,"file":"admin/components/ContentEntries/Filters/RefFieldRenderer.js","sources":["../../../../../src/admin/components/ContentEntries/Filters/RefFieldRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Bind } from \"@webiny/form\";\nimport type { BindComponentRenderProp } from \"@webiny/form\";\nimport { AutoComplete } from \"@webiny/admin-ui\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { ContentEntryListConfig } from \"~/admin/config/contentEntries/index.js\";\nimport { RefSingleAutocompletePresenterFeature } from \"~/presentation/fieldRenderers/ref/autocomplete/single/feature.js\";\n\nconst { useInputField } = ContentEntryListConfig.Browser.AdvancedSearch.FieldRenderer;\n\n/**\n * The advanced search filter stores the selected reference as a JSON string\n * `{ entryId, modelId }`. `GraphQLInputMapper` reads `entryId` from it to build the\n * `<fieldId>.entryId` where condition, and `modelId` is used to load the label back\n * when a saved filter is opened.\n */\ninterface RefFilterValue {\n entryId: string;\n modelId: string;\n}\n\nconst parseValue = (value: unknown): RefFilterValue | null => {\n if (typeof value !== \"string\" || !value) {\n return null;\n }\n try {\n const parsed = JSON.parse(value);\n if (!parsed?.entryId || !parsed?.modelId) {\n return null;\n }\n return { entryId: parsed.entryId, modelId: parsed.modelId };\n } catch {\n return null;\n }\n};\n\nexport const Ref = () => {\n const { name, field } = useInputField();\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefSingleAutocompletePresenterFeature.register(child);\n return child;\n }, [field.value]);\n\n return (\n <DiContainerProvider container={scopedContainer} key={field.value}>\n <Bind name={name}>\n {bind => <RefInner bind={bind} modelIds={field.settings.modelIds} />}\n </Bind>\n </DiContainerProvider>\n );\n};\n\ninterface RefInnerProps {\n bind: BindComponentRenderProp;\n modelIds: string[];\n}\n\nconst RefInner = observer(({ bind, modelIds }: RefInnerProps) => {\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n useEffect(() => {\n const value = parseValue(bind.value);\n presenter.init({\n modelIds,\n value: value ? { id: value.entryId, modelId: value.modelId } : null\n });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={\"Value\"}\n size={\"lg\"}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n validation={bind.validation}\n shouldFilter={false}\n onValueSearch={query => presenter.search(query)}\n onValueChange={entryId => {\n if (!entryId) {\n presenter.clear();\n bind.onChange(\"\");\n return;\n }\n const ref = presenter.select(entryId);\n if (ref) {\n bind.onChange(JSON.stringify({ entryId, modelId: ref.modelId }));\n }\n }}\n onValueReset={() => {\n presenter.clear();\n bind.onChange(\"\");\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n"],"names":["useInputField","ContentEntryListConfig","parseValue","value","parsed","JSON","Ref","name","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefSingleAutocompletePresenterFeature","DiContainerProvider","Bind","bind","RefInner","observer","modelIds","presenter","useFeature","useEffect","vm","AutoComplete","query","entryId","ref"],"mappings":";;;;;;;AASA,MAAM,EAAEA,eAAAA,aAAa,EAAE,GAAGC,uBAAuB,OAAO,CAAC,cAAc,CAAC,aAAa;AAarF,MAAMC,aAAa,CAACC;IAChB,IAAI,AAAiB,YAAjB,OAAOA,SAAsB,CAACA,OAC9B,OAAO;IAEX,IAAI;QACA,MAAMC,SAASC,KAAK,KAAK,CAACF;QAC1B,IAAI,CAACC,QAAQ,WAAW,CAACA,QAAQ,SAC7B,OAAO;QAEX,OAAO;YAAE,SAASA,OAAO,OAAO;YAAE,SAASA,OAAO,OAAO;QAAC;IAC9D,EAAE,OAAM;QACJ,OAAO;IACX;AACJ;AAEO,MAAME,MAAM;IACf,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,GAAGR;IACxB,MAAMS,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,sCAAsC,QAAQ,CAACD;QAC/C,OAAOA;IACX,GAAG;QAACL,MAAM,KAAK;KAAC;IAEhB,OAAO,WAAP,GACI,oBAACO,qBAAmBA;QAAC,WAAWJ;QAAiB,KAAKH,MAAM,KAAK;qBAC7D,oBAACQ,MAAIA;QAAC,MAAMT;OACPU,CAAAA,OAAAA,WAAAA,GAAQ,oBAACC,UAAQA;YAAC,MAAMD;YAAM,UAAUT,MAAM,QAAQ,CAAC,QAAQ;;AAIhF;AAOA,MAAMU,WAAWC,SAAS,CAAC,EAAEF,IAAI,EAAEG,QAAQ,EAAiB;IACxD,MAAM,EAAEC,SAAS,EAAE,GAAGC,WAAWR;IAEjCS,UAAU;QACN,MAAMpB,QAAQD,WAAWe,KAAK,KAAK;QACnCI,UAAU,IAAI,CAAC;YACXD;YACA,OAAOjB,QAAQ;gBAAE,IAAIA,MAAM,OAAO;gBAAE,SAASA,MAAM,OAAO;YAAC,IAAI;QACnE;IACJ,GAAG,EAAE;IAEL,MAAMqB,KAAKH,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACI,cAAYA;QACT,OAAO;QACP,MAAM;QACN,SAASD,GAAG,OAAO;QACnB,OAAOA,GAAG,KAAK;QACf,SAASA,GAAG,OAAO;QACnB,YAAYP,KAAK,UAAU;QAC3B,cAAc;QACd,eAAeS,CAAAA,QAASL,UAAU,MAAM,CAACK;QACzC,eAAeC,CAAAA;YACX,IAAI,CAACA,SAAS;gBACVN,UAAU,KAAK;gBACfJ,KAAK,QAAQ,CAAC;gBACd;YACJ;YACA,MAAMW,MAAMP,UAAU,MAAM,CAACM;YAC7B,IAAIC,KACAX,KAAK,QAAQ,CAACZ,KAAK,SAAS,CAAC;gBAAEsB;gBAAS,SAASC,IAAI,OAAO;YAAC;QAErE;QACA,cAAc;YACVP,UAAU,KAAK;YACfJ,KAAK,QAAQ,CAAC;QAClB;QACA,oBAAoBO,GAAG,QAAQ;;AAG3C"}
|
|
@@ -21,11 +21,9 @@ const Name = ()=>{
|
|
|
21
21
|
"alt+cmd+enter": ()=>setEditing(true)
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
-
const autoFocus = !window.Cypress;
|
|
25
24
|
return /*#__PURE__*/ react.createElement(EditableTitle, {
|
|
26
25
|
value: data.name,
|
|
27
26
|
onCommit: saveName,
|
|
28
|
-
autoFocus: autoFocus,
|
|
29
27
|
isEditing: editing,
|
|
30
28
|
onEditingChange: setEditing,
|
|
31
29
|
tooltip: /*#__PURE__*/ react.createElement("span", null, t`Rename content model`),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin/plugins/editor/defaultBar/Name/Name.js","sources":["../../../../../../src/admin/plugins/editor/defaultBar/Name/Name.tsx"],"sourcesContent":["import React, { useCallback, useState } from \"react\";\nimport { useHotkeys } from \"@webiny/app-admin\";\nimport { i18n } from \"@webiny/app/i18n/index.js\";\nimport { useModelEditor } from \"~/admin/hooks/index.js\";\nimport { EditableTitle } from \"@webiny/admin-ui\";\n\nconst t = i18n.namespace(\"ContentModelEditor.Name\");\n\
|
|
1
|
+
{"version":3,"file":"admin/plugins/editor/defaultBar/Name/Name.js","sources":["../../../../../../src/admin/plugins/editor/defaultBar/Name/Name.tsx"],"sourcesContent":["import React, { useCallback, useState } from \"react\";\nimport { useHotkeys } from \"@webiny/app-admin\";\nimport { i18n } from \"@webiny/app/i18n/index.js\";\nimport { useModelEditor } from \"~/admin/hooks/index.js\";\nimport { EditableTitle } from \"@webiny/admin-ui\";\n\nconst t = i18n.namespace(\"ContentModelEditor.Name\");\n\nexport const Name = () => {\n const { data, setData } = useModelEditor();\n const [editing, setEditing] = useState<boolean>(false);\n\n const saveName = useCallback(\n (name: string) => {\n setData(data => {\n data.name = name;\n return data;\n });\n },\n [setData]\n );\n\n useHotkeys({\n zIndex: 100,\n keys: {\n \"alt+cmd+enter\": () => setEditing(true)\n }\n });\n\n return (\n <EditableTitle\n value={data.name}\n onCommit={saveName}\n isEditing={editing}\n onEditingChange={setEditing}\n tooltip={<span>{t`Rename content model`}</span>}\n data-testid=\"cms-editor-model-title\"\n />\n );\n};\n"],"names":["t","i18n","Name","data","setData","useModelEditor","editing","setEditing","useState","saveName","useCallback","name","useHotkeys","EditableTitle"],"mappings":";;;;;AAMA,MAAMA,IAAIC,KAAK,SAAS,CAAC;AAElB,MAAMC,OAAO;IAChB,MAAM,EAAEC,IAAI,EAAEC,OAAO,EAAE,GAAGC;IAC1B,MAAM,CAACC,SAASC,WAAW,GAAGC,SAAkB;IAEhD,MAAMC,WAAWC,YACb,CAACC;QACGP,QAAQD,CAAAA;YACJA,KAAK,IAAI,GAAGQ;YACZ,OAAOR;QACX;IACJ,GACA;QAACC;KAAQ;IAGbQ,WAAW;QACP,QAAQ;QACR,MAAM;YACF,iBAAiB,IAAML,WAAW;QACtC;IACJ;IAEA,OAAO,WAAP,GACI,oBAACM,eAAaA;QACV,OAAOV,KAAK,IAAI;QAChB,UAAUM;QACV,WAAWH;QACX,iBAAiBC;QACjB,uBAAS,oBAAC,cAAMP,CAAC,CAAC,oBAAoB,CAAC;QACvC,eAAY;;AAGxB"}
|
|
@@ -18,6 +18,7 @@ import { BulkActionFeature } from "./bulkAction/feature.js";
|
|
|
18
18
|
import { CmsTrashBinFeature } from "./trashBin/feature.js";
|
|
19
19
|
import { SearchContentEntriesFeature } from "./searchContentEntries/feature.js";
|
|
20
20
|
import { GetContentEntriesFeature } from "./getContentEntries/feature.js";
|
|
21
|
+
import { ReferenceTitlesFeature } from "./referenceTitles/feature.js";
|
|
21
22
|
import { ValueTransformersFeature } from "./valueTransformers/feature.js";
|
|
22
23
|
import { EntryGraphQLFields } from "./EntryGraphQLFields.js";
|
|
23
24
|
const ContentEntryFeature = createFeature({
|
|
@@ -30,6 +31,7 @@ const ContentEntryFeature = createFeature({
|
|
|
30
31
|
ListEntriesFeature.register(container);
|
|
31
32
|
SearchContentEntriesFeature.register(container);
|
|
32
33
|
GetContentEntriesFeature.register(container);
|
|
34
|
+
ReferenceTitlesFeature.register(container);
|
|
33
35
|
CreateEntryFeature.register(container);
|
|
34
36
|
DuplicateEntryFeature.register(container);
|
|
35
37
|
UpdateEntryFeature.register(container);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/contentEntry/feature.js","sources":["../../../src/features/contentEntry/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { ContentEntriesCacheProvider } from \"./ContentEntriesCacheProvider.js\";\nimport { GetEntryFeature } from \"./getEntry/feature.js\";\nimport { ListEntriesFeature } from \"./listEntries/feature.js\";\nimport { CreateEntryFeature } from \"./createEntry/feature.js\";\nimport { DuplicateEntryFeature } from \"./duplicateEntry/feature.js\";\nimport { UpdateEntryFeature } from \"./updateEntry/feature.js\";\nimport { PublishEntryFeature } from \"./publishEntry/feature.js\";\nimport { UnpublishEntryFeature } from \"./unpublishEntry/feature.js\";\nimport { DeleteEntryFeature } from \"./deleteEntry/feature.js\";\nimport { DeleteEntryRevisionFeature } from \"./deleteEntryRevision/feature.js\";\nimport { CreateRevisionFromFeature } from \"./createRevisionFrom/feature.js\";\nimport { ListRevisionsFeature } from \"./listRevisions/feature.js\";\nimport { UpdateRevisionDescriptionFeature } from \"./updateRevisionDescription/feature.js\";\nimport { SingletonEntryFeature } from \"./singletonEntry/feature.js\";\nimport { MoveEntryFeature } from \"./moveEntry/feature.js\";\nimport { BulkActionFeature } from \"./bulkAction/feature.js\";\nimport { CmsTrashBinFeature } from \"./trashBin/feature.js\";\nimport { SearchContentEntriesFeature } from \"./searchContentEntries/feature.js\";\nimport { GetContentEntriesFeature } from \"./getContentEntries/feature.js\";\nimport { ValueTransformersFeature } from \"./valueTransformers/feature.js\";\nimport { EntryGraphQLFields } from \"./EntryGraphQLFields.js\";\n\nexport const ContentEntryFeature = createFeature({\n name: \"CmsContentEntry\",\n register(container) {\n container.register(EntryGraphQLFields).inSingletonScope();\n container.register(ContentEntriesCacheProvider).inSingletonScope();\n\n ValueTransformersFeature.register(container);\n GetEntryFeature.register(container);\n ListEntriesFeature.register(container);\n SearchContentEntriesFeature.register(container);\n GetContentEntriesFeature.register(container);\n CreateEntryFeature.register(container);\n DuplicateEntryFeature.register(container);\n UpdateEntryFeature.register(container);\n PublishEntryFeature.register(container);\n UnpublishEntryFeature.register(container);\n DeleteEntryFeature.register(container);\n DeleteEntryRevisionFeature.register(container);\n CreateRevisionFromFeature.register(container);\n ListRevisionsFeature.register(container);\n UpdateRevisionDescriptionFeature.register(container);\n SingletonEntryFeature.register(container);\n MoveEntryFeature.register(container);\n BulkActionFeature.register(container);\n CmsTrashBinFeature.register(container);\n }\n});\n"],"names":["ContentEntryFeature","createFeature","container","EntryGraphQLFields","ContentEntriesCacheProvider","ValueTransformersFeature","GetEntryFeature","ListEntriesFeature","SearchContentEntriesFeature","GetContentEntriesFeature","CreateEntryFeature","DuplicateEntryFeature","UpdateEntryFeature","PublishEntryFeature","UnpublishEntryFeature","DeleteEntryFeature","DeleteEntryRevisionFeature","CreateRevisionFromFeature","ListRevisionsFeature","UpdateRevisionDescriptionFeature","SingletonEntryFeature","MoveEntryFeature","BulkActionFeature","CmsTrashBinFeature"],"mappings":"
|
|
1
|
+
{"version":3,"file":"features/contentEntry/feature.js","sources":["../../../src/features/contentEntry/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { ContentEntriesCacheProvider } from \"./ContentEntriesCacheProvider.js\";\nimport { GetEntryFeature } from \"./getEntry/feature.js\";\nimport { ListEntriesFeature } from \"./listEntries/feature.js\";\nimport { CreateEntryFeature } from \"./createEntry/feature.js\";\nimport { DuplicateEntryFeature } from \"./duplicateEntry/feature.js\";\nimport { UpdateEntryFeature } from \"./updateEntry/feature.js\";\nimport { PublishEntryFeature } from \"./publishEntry/feature.js\";\nimport { UnpublishEntryFeature } from \"./unpublishEntry/feature.js\";\nimport { DeleteEntryFeature } from \"./deleteEntry/feature.js\";\nimport { DeleteEntryRevisionFeature } from \"./deleteEntryRevision/feature.js\";\nimport { CreateRevisionFromFeature } from \"./createRevisionFrom/feature.js\";\nimport { ListRevisionsFeature } from \"./listRevisions/feature.js\";\nimport { UpdateRevisionDescriptionFeature } from \"./updateRevisionDescription/feature.js\";\nimport { SingletonEntryFeature } from \"./singletonEntry/feature.js\";\nimport { MoveEntryFeature } from \"./moveEntry/feature.js\";\nimport { BulkActionFeature } from \"./bulkAction/feature.js\";\nimport { CmsTrashBinFeature } from \"./trashBin/feature.js\";\nimport { SearchContentEntriesFeature } from \"./searchContentEntries/feature.js\";\nimport { GetContentEntriesFeature } from \"./getContentEntries/feature.js\";\nimport { ReferenceTitlesFeature } from \"./referenceTitles/feature.js\";\nimport { ValueTransformersFeature } from \"./valueTransformers/feature.js\";\nimport { EntryGraphQLFields } from \"./EntryGraphQLFields.js\";\n\nexport const ContentEntryFeature = createFeature({\n name: \"CmsContentEntry\",\n register(container) {\n container.register(EntryGraphQLFields).inSingletonScope();\n container.register(ContentEntriesCacheProvider).inSingletonScope();\n\n ValueTransformersFeature.register(container);\n GetEntryFeature.register(container);\n ListEntriesFeature.register(container);\n SearchContentEntriesFeature.register(container);\n GetContentEntriesFeature.register(container);\n ReferenceTitlesFeature.register(container);\n CreateEntryFeature.register(container);\n DuplicateEntryFeature.register(container);\n UpdateEntryFeature.register(container);\n PublishEntryFeature.register(container);\n UnpublishEntryFeature.register(container);\n DeleteEntryFeature.register(container);\n DeleteEntryRevisionFeature.register(container);\n CreateRevisionFromFeature.register(container);\n ListRevisionsFeature.register(container);\n UpdateRevisionDescriptionFeature.register(container);\n SingletonEntryFeature.register(container);\n MoveEntryFeature.register(container);\n BulkActionFeature.register(container);\n CmsTrashBinFeature.register(container);\n }\n});\n"],"names":["ContentEntryFeature","createFeature","container","EntryGraphQLFields","ContentEntriesCacheProvider","ValueTransformersFeature","GetEntryFeature","ListEntriesFeature","SearchContentEntriesFeature","GetContentEntriesFeature","ReferenceTitlesFeature","CreateEntryFeature","DuplicateEntryFeature","UpdateEntryFeature","PublishEntryFeature","UnpublishEntryFeature","DeleteEntryFeature","DeleteEntryRevisionFeature","CreateRevisionFromFeature","ListRevisionsFeature","UpdateRevisionDescriptionFeature","SingletonEntryFeature","MoveEntryFeature","BulkActionFeature","CmsTrashBinFeature"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBO,MAAMA,sBAAsBC,cAAc;IAC7C,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC,oBAAoB,gBAAgB;QACvDD,UAAU,QAAQ,CAACE,6BAA6B,gBAAgB;QAEhEC,yBAAyB,QAAQ,CAACH;QAClCI,gBAAgB,QAAQ,CAACJ;QACzBK,mBAAmB,QAAQ,CAACL;QAC5BM,4BAA4B,QAAQ,CAACN;QACrCO,yBAAyB,QAAQ,CAACP;QAClCQ,uBAAuB,QAAQ,CAACR;QAChCS,mBAAmB,QAAQ,CAACT;QAC5BU,sBAAsB,QAAQ,CAACV;QAC/BW,mBAAmB,QAAQ,CAACX;QAC5BY,oBAAoB,QAAQ,CAACZ;QAC7Ba,sBAAsB,QAAQ,CAACb;QAC/Bc,mBAAmB,QAAQ,CAACd;QAC5Be,2BAA2B,QAAQ,CAACf;QACpCgB,0BAA0B,QAAQ,CAAChB;QACnCiB,qBAAqB,QAAQ,CAACjB;QAC9BkB,iCAAiC,QAAQ,CAAClB;QAC1CmB,sBAAsB,QAAQ,CAACnB;QAC/BoB,iBAAiB,QAAQ,CAACpB;QAC1BqB,kBAAkB,QAAQ,CAACrB;QAC3BsB,mBAAmB,QAAQ,CAACtB;IAChC;AACJ"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { GetContentEntriesGateway } from "../getContentEntries/abstractions.js";
|
|
2
|
+
import type { CmsReferenceEntry, CmsReferenceValue } from "../refTypes.js";
|
|
3
|
+
import { ReferenceTitles as Abstraction } from "./abstractions.js";
|
|
4
|
+
declare class ReferenceTitlesImpl implements Abstraction.Interface {
|
|
5
|
+
private gateway;
|
|
6
|
+
private readonly titles;
|
|
7
|
+
private readonly requested;
|
|
8
|
+
private queue;
|
|
9
|
+
constructor(gateway: GetContentEntriesGateway.Interface);
|
|
10
|
+
getTitle(ref: CmsReferenceValue): string | undefined;
|
|
11
|
+
remember(entries: CmsReferenceEntry[]): void;
|
|
12
|
+
private enqueue;
|
|
13
|
+
private flush;
|
|
14
|
+
}
|
|
15
|
+
export declare const ReferenceTitles: typeof ReferenceTitlesImpl & {
|
|
16
|
+
__abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").IReferenceTitles>;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { observable, runInAction } from "mobx";
|
|
2
|
+
import { GetContentEntriesGateway } from "../getContentEntries/abstractions.js";
|
|
3
|
+
import { ReferenceTitles } from "./abstractions.js";
|
|
4
|
+
function toKey(modelId, id) {
|
|
5
|
+
return `${modelId}:${id.split("#")[0]}`;
|
|
6
|
+
}
|
|
7
|
+
class ReferenceTitlesImpl {
|
|
8
|
+
constructor(gateway){
|
|
9
|
+
this.gateway = gateway;
|
|
10
|
+
this.titles = observable.map();
|
|
11
|
+
this.requested = new Set();
|
|
12
|
+
this.queue = [];
|
|
13
|
+
}
|
|
14
|
+
getTitle(ref) {
|
|
15
|
+
const key = toKey(ref.modelId, ref.id);
|
|
16
|
+
if (!this.titles.has(key) && !this.requested.has(key)) {
|
|
17
|
+
this.requested.add(key);
|
|
18
|
+
this.enqueue(ref);
|
|
19
|
+
}
|
|
20
|
+
return this.titles.get(key);
|
|
21
|
+
}
|
|
22
|
+
remember(entries) {
|
|
23
|
+
runInAction(()=>{
|
|
24
|
+
for (const entry of entries)this.titles.set(toKey(entry.model.modelId, entry.entryId), entry.title);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
enqueue(ref) {
|
|
28
|
+
this.queue.push(ref);
|
|
29
|
+
if (this.queue.length > 1) return;
|
|
30
|
+
setTimeout(()=>this.flush(), 0);
|
|
31
|
+
}
|
|
32
|
+
async flush() {
|
|
33
|
+
const entries = this.queue;
|
|
34
|
+
this.queue = [];
|
|
35
|
+
const keys = entries.map((ref)=>toKey(ref.modelId, ref.id));
|
|
36
|
+
try {
|
|
37
|
+
const result = await this.gateway.execute({
|
|
38
|
+
entries
|
|
39
|
+
});
|
|
40
|
+
this.remember(result.latest);
|
|
41
|
+
runInAction(()=>{
|
|
42
|
+
for (const key of keys)if (!this.titles.has(key)) this.titles.set(key, "");
|
|
43
|
+
});
|
|
44
|
+
} catch (e) {
|
|
45
|
+
for (const key of keys)this.requested.delete(key);
|
|
46
|
+
console.error(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const ReferenceTitles_ReferenceTitles = ReferenceTitles.createImplementation({
|
|
51
|
+
implementation: ReferenceTitlesImpl,
|
|
52
|
+
dependencies: [
|
|
53
|
+
GetContentEntriesGateway
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
export { ReferenceTitles_ReferenceTitles as ReferenceTitles };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=ReferenceTitles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/contentEntry/referenceTitles/ReferenceTitles.js","sources":["../../../../src/features/contentEntry/referenceTitles/ReferenceTitles.ts"],"sourcesContent":["import { observable, runInAction } from \"mobx\";\nimport { GetContentEntriesGateway } from \"../getContentEntries/abstractions.js\";\nimport type { CmsReferenceEntry, CmsReferenceValue } from \"../refTypes.js\";\nimport { ReferenceTitles as Abstraction } from \"./abstractions.js\";\n\n// A reference points at a revision (`entryId#0001`), but the title shown is the latest one, so\n// every revision of an entry shares a key.\nfunction toKey(modelId: string, id: string): string {\n return `${modelId}:${id.split(\"#\")[0]}`;\n}\n\nclass ReferenceTitlesImpl implements Abstraction.Interface {\n private readonly titles = observable.map<string, string>();\n private readonly requested = new Set<string>();\n private queue: CmsReferenceValue[] = [];\n\n constructor(private gateway: GetContentEntriesGateway.Interface) {}\n\n getTitle(ref: CmsReferenceValue): string | undefined {\n const key = toKey(ref.modelId, ref.id);\n if (!this.titles.has(key) && !this.requested.has(key)) {\n this.requested.add(key);\n this.enqueue(ref);\n }\n return this.titles.get(key);\n }\n\n remember(entries: CmsReferenceEntry[]): void {\n runInAction(() => {\n for (const entry of entries) {\n this.titles.set(toKey(entry.model.modelId, entry.entryId), entry.title);\n }\n });\n }\n\n private enqueue(ref: CmsReferenceValue): void {\n this.queue.push(ref);\n if (this.queue.length > 1) {\n return;\n }\n // Wait for the render pass to finish asking, then load everything it asked for at once.\n setTimeout(() => this.flush(), 0);\n }\n\n private async flush(): Promise<void> {\n const entries = this.queue;\n this.queue = [];\n const keys = entries.map(ref => toKey(ref.modelId, ref.id));\n try {\n const result = await this.gateway.execute({ entries });\n this.remember(result.latest);\n // A deleted entry has no title. Settle it as empty, so a title made of several\n // references doesn't wait on it forever.\n runInAction(() => {\n for (const key of keys) {\n if (!this.titles.has(key)) {\n this.titles.set(key, \"\");\n }\n }\n });\n } catch (e) {\n // The title falls back to the item's default label until the next render asks again.\n for (const key of keys) {\n this.requested.delete(key);\n }\n console.error(e);\n }\n }\n}\n\nexport const ReferenceTitles = Abstraction.createImplementation({\n implementation: ReferenceTitlesImpl,\n dependencies: [GetContentEntriesGateway]\n});\n"],"names":["toKey","modelId","id","ReferenceTitlesImpl","gateway","observable","Set","ref","key","entries","runInAction","entry","setTimeout","keys","result","e","console","ReferenceTitles","Abstraction","GetContentEntriesGateway"],"mappings":";;;AAOA,SAASA,MAAMC,OAAe,EAAEC,EAAU;IACtC,OAAO,GAAGD,QAAQ,CAAC,EAAEC,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE;AAC3C;AAEA,MAAMC;IAKF,YAAoBC,OAA2C,CAAE;aAA7CA,OAAO,GAAPA;aAJH,MAAM,GAAGC,WAAW,GAAG;aACvB,SAAS,GAAG,IAAIC;aACzB,KAAK,GAAwB,EAAE;IAE2B;IAElE,SAASC,GAAsB,EAAsB;QACjD,MAAMC,MAAMR,MAAMO,IAAI,OAAO,EAAEA,IAAI,EAAE;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAACC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAACA,MAAM;YACnD,IAAI,CAAC,SAAS,CAAC,GAAG,CAACA;YACnB,IAAI,CAAC,OAAO,CAACD;QACjB;QACA,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAACC;IAC3B;IAEA,SAASC,OAA4B,EAAQ;QACzCC,YAAY;YACR,KAAK,MAAMC,SAASF,QAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CAACT,MAAMW,MAAM,KAAK,CAAC,OAAO,EAAEA,MAAM,OAAO,GAAGA,MAAM,KAAK;QAE9E;IACJ;IAEQ,QAAQJ,GAAsB,EAAQ;QAC1C,IAAI,CAAC,KAAK,CAAC,IAAI,CAACA;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GACpB;QAGJK,WAAW,IAAM,IAAI,CAAC,KAAK,IAAI;IACnC;IAEA,MAAc,QAAuB;QACjC,MAAMH,UAAU,IAAI,CAAC,KAAK;QAC1B,IAAI,CAAC,KAAK,GAAG,EAAE;QACf,MAAMI,OAAOJ,QAAQ,GAAG,CAACF,CAAAA,MAAOP,MAAMO,IAAI,OAAO,EAAEA,IAAI,EAAE;QACzD,IAAI;YACA,MAAMO,SAAS,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAEL;YAAQ;YACpD,IAAI,CAAC,QAAQ,CAACK,OAAO,MAAM;YAG3BJ,YAAY;gBACR,KAAK,MAAMF,OAAOK,KACd,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAACL,MACjB,IAAI,CAAC,MAAM,CAAC,GAAG,CAACA,KAAK;YAGjC;QACJ,EAAE,OAAOO,GAAG;YAER,KAAK,MAAMP,OAAOK,KACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAACL;YAE1BQ,QAAQ,KAAK,CAACD;QAClB;IACJ;AACJ;AAEO,MAAME,kCAAkBC,gBAAAA,oBAAgC,CAAC;IAC5D,gBAAgBf;IAChB,cAAc;QAACgB;KAAyB;AAC5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Container } from "@webiny/di";
|
|
3
|
+
import { GetContentEntriesGateway, GetContentEntriesUseCase } from "../getContentEntries/abstractions.js";
|
|
4
|
+
import { SearchContentEntriesUseCase } from "../searchContentEntries/abstractions.js";
|
|
5
|
+
import { ReferenceTitles } from "./abstractions.js";
|
|
6
|
+
import { ReferenceTitlesFeature } from "./feature.js";
|
|
7
|
+
const createEntry = (modelId, entryId, title)=>({
|
|
8
|
+
id: `${entryId}#0002`,
|
|
9
|
+
entryId,
|
|
10
|
+
status: "draft",
|
|
11
|
+
title,
|
|
12
|
+
description: null,
|
|
13
|
+
image: null,
|
|
14
|
+
createdOn: "2026-01-01T00:00:00.000Z",
|
|
15
|
+
savedOn: "2026-01-01T00:00:00.000Z",
|
|
16
|
+
createdBy: {
|
|
17
|
+
id: "1",
|
|
18
|
+
type: "admin",
|
|
19
|
+
displayName: "Admin"
|
|
20
|
+
},
|
|
21
|
+
modifiedBy: null,
|
|
22
|
+
model: {
|
|
23
|
+
modelId,
|
|
24
|
+
name: modelId
|
|
25
|
+
},
|
|
26
|
+
published: null,
|
|
27
|
+
wbyAco_location: null
|
|
28
|
+
});
|
|
29
|
+
const entries = [
|
|
30
|
+
createEntry("location", "baker", "Baker"),
|
|
31
|
+
createEntry("location", "lincoln", "Lincoln Park"),
|
|
32
|
+
createEntry("coach", "ana", "Ana")
|
|
33
|
+
];
|
|
34
|
+
const createContainer = ()=>{
|
|
35
|
+
const gateway = vi.fn(async ({ entries: refs })=>({
|
|
36
|
+
latest: entries.filter((e)=>refs.some((r)=>r.id.startsWith(e.entryId))),
|
|
37
|
+
published: []
|
|
38
|
+
}));
|
|
39
|
+
const container = new Container();
|
|
40
|
+
container.registerInstance(GetContentEntriesGateway, {
|
|
41
|
+
execute: gateway
|
|
42
|
+
});
|
|
43
|
+
container.registerInstance(GetContentEntriesUseCase, {
|
|
44
|
+
execute: async ()=>({
|
|
45
|
+
latest: [
|
|
46
|
+
entries[0]
|
|
47
|
+
],
|
|
48
|
+
published: []
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
container.registerInstance(SearchContentEntriesUseCase, {
|
|
52
|
+
execute: async ()=>({
|
|
53
|
+
data: [
|
|
54
|
+
entries[1]
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
ReferenceTitlesFeature.register(container);
|
|
59
|
+
return {
|
|
60
|
+
container,
|
|
61
|
+
gateway,
|
|
62
|
+
titles: container.resolve(ReferenceTitles)
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const tick = ()=>new Promise((resolve)=>setTimeout(resolve, 0));
|
|
66
|
+
describe("ReferenceTitles", ()=>{
|
|
67
|
+
it("loads every reference asked for in the same tick with one request", async ()=>{
|
|
68
|
+
const { titles, gateway } = createContainer();
|
|
69
|
+
expect(titles.getTitle({
|
|
70
|
+
modelId: "location",
|
|
71
|
+
id: "baker#0001"
|
|
72
|
+
})).toBeUndefined();
|
|
73
|
+
expect(titles.getTitle({
|
|
74
|
+
modelId: "location",
|
|
75
|
+
id: "lincoln#0001"
|
|
76
|
+
})).toBeUndefined();
|
|
77
|
+
expect(titles.getTitle({
|
|
78
|
+
modelId: "coach",
|
|
79
|
+
id: "ana#0001"
|
|
80
|
+
})).toBeUndefined();
|
|
81
|
+
await tick();
|
|
82
|
+
await tick();
|
|
83
|
+
expect(gateway).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(gateway.mock.calls[0][0].entries).toHaveLength(3);
|
|
85
|
+
expect(titles.getTitle({
|
|
86
|
+
modelId: "location",
|
|
87
|
+
id: "baker#0001"
|
|
88
|
+
})).toBe("Baker");
|
|
89
|
+
expect(titles.getTitle({
|
|
90
|
+
modelId: "coach",
|
|
91
|
+
id: "ana#0001"
|
|
92
|
+
})).toBe("Ana");
|
|
93
|
+
});
|
|
94
|
+
it("asks for a reference once, whichever revision it points at", async ()=>{
|
|
95
|
+
const { titles, gateway } = createContainer();
|
|
96
|
+
titles.getTitle({
|
|
97
|
+
modelId: "location",
|
|
98
|
+
id: "baker#0001"
|
|
99
|
+
});
|
|
100
|
+
await tick();
|
|
101
|
+
await tick();
|
|
102
|
+
titles.getTitle({
|
|
103
|
+
modelId: "location",
|
|
104
|
+
id: "baker#0001"
|
|
105
|
+
});
|
|
106
|
+
titles.getTitle({
|
|
107
|
+
modelId: "location",
|
|
108
|
+
id: "baker#0003"
|
|
109
|
+
});
|
|
110
|
+
await tick();
|
|
111
|
+
expect(gateway).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
it("settles a reference to a deleted entry as an empty title", async ()=>{
|
|
114
|
+
const { titles, gateway } = createContainer();
|
|
115
|
+
titles.getTitle({
|
|
116
|
+
modelId: "location",
|
|
117
|
+
id: "gone#0001"
|
|
118
|
+
});
|
|
119
|
+
await tick();
|
|
120
|
+
await tick();
|
|
121
|
+
expect(titles.getTitle({
|
|
122
|
+
modelId: "location",
|
|
123
|
+
id: "gone#0001"
|
|
124
|
+
})).toBe("");
|
|
125
|
+
expect(gateway).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
it("asks again after a failed request", async ()=>{
|
|
128
|
+
const { titles, gateway } = createContainer();
|
|
129
|
+
gateway.mockRejectedValueOnce(new Error("Network error"));
|
|
130
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(()=>void 0);
|
|
131
|
+
titles.getTitle({
|
|
132
|
+
modelId: "location",
|
|
133
|
+
id: "baker#0001"
|
|
134
|
+
});
|
|
135
|
+
await tick();
|
|
136
|
+
await tick();
|
|
137
|
+
expect(titles.getTitle({
|
|
138
|
+
modelId: "location",
|
|
139
|
+
id: "baker#0001"
|
|
140
|
+
})).toBeUndefined();
|
|
141
|
+
await tick();
|
|
142
|
+
await tick();
|
|
143
|
+
expect(gateway).toHaveBeenCalledTimes(2);
|
|
144
|
+
expect(titles.getTitle({
|
|
145
|
+
modelId: "location",
|
|
146
|
+
id: "baker#0001"
|
|
147
|
+
})).toBe("Baker");
|
|
148
|
+
consoleError.mockRestore();
|
|
149
|
+
});
|
|
150
|
+
it("keeps titles of entries the reference inputs load, without a request of its own", async ()=>{
|
|
151
|
+
const { container, titles, gateway } = createContainer();
|
|
152
|
+
await container.resolve(GetContentEntriesUseCase).execute({
|
|
153
|
+
entries: []
|
|
154
|
+
});
|
|
155
|
+
await container.resolve(SearchContentEntriesUseCase).execute({
|
|
156
|
+
modelIds: [
|
|
157
|
+
"location"
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
expect(titles.getTitle({
|
|
161
|
+
modelId: "location",
|
|
162
|
+
id: "baker#0001"
|
|
163
|
+
})).toBe("Baker");
|
|
164
|
+
expect(titles.getTitle({
|
|
165
|
+
modelId: "location",
|
|
166
|
+
id: "lincoln#0001"
|
|
167
|
+
})).toBe("Lincoln Park");
|
|
168
|
+
await tick();
|
|
169
|
+
expect(gateway).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=ReferenceTitles.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/contentEntry/referenceTitles/ReferenceTitles.test.js","sources":["../../../../src/features/contentEntry/referenceTitles/ReferenceTitles.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport {\n GetContentEntriesGateway,\n GetContentEntriesUseCase\n} from \"~/features/contentEntry/getContentEntries/abstractions.js\";\nimport { SearchContentEntriesUseCase } from \"~/features/contentEntry/searchContentEntries/abstractions.js\";\nimport type { CmsReferenceEntry } from \"~/features/contentEntry/refTypes.js\";\nimport { ReferenceTitles as Abstraction } from \"./abstractions.js\";\nimport { ReferenceTitlesFeature } from \"./feature.js\";\n\nconst createEntry = (modelId: string, entryId: string, title: string): CmsReferenceEntry => ({\n id: `${entryId}#0002`,\n entryId,\n status: \"draft\",\n title,\n description: null,\n image: null,\n createdOn: \"2026-01-01T00:00:00.000Z\",\n savedOn: \"2026-01-01T00:00:00.000Z\",\n createdBy: { id: \"1\", type: \"admin\", displayName: \"Admin\" },\n modifiedBy: null,\n model: { modelId, name: modelId },\n published: null,\n wbyAco_location: null\n});\n\nconst entries = [\n createEntry(\"location\", \"baker\", \"Baker\"),\n createEntry(\"location\", \"lincoln\", \"Lincoln Park\"),\n createEntry(\"coach\", \"ana\", \"Ana\")\n];\n\nconst createContainer = () => {\n const gateway = vi.fn(async ({ entries: refs }: { entries: { id: string }[] }) => ({\n latest: entries.filter(e => refs.some(r => r.id.startsWith(e.entryId))),\n published: []\n }));\n const container = new Container();\n container.registerInstance(GetContentEntriesGateway, { execute: gateway });\n container.registerInstance(GetContentEntriesUseCase, {\n execute: async () => ({ latest: [entries[0]], published: [] })\n });\n container.registerInstance(SearchContentEntriesUseCase, {\n execute: async () => ({ data: [entries[1]] })\n });\n ReferenceTitlesFeature.register(container);\n return { container, gateway, titles: container.resolve(Abstraction) };\n};\n\nconst tick = () => new Promise(resolve => setTimeout(resolve, 0));\n\ndescribe(\"ReferenceTitles\", () => {\n it(\"loads every reference asked for in the same tick with one request\", async () => {\n const { titles, gateway } = createContainer();\n\n expect(titles.getTitle({ modelId: \"location\", id: \"baker#0001\" })).toBeUndefined();\n expect(titles.getTitle({ modelId: \"location\", id: \"lincoln#0001\" })).toBeUndefined();\n expect(titles.getTitle({ modelId: \"coach\", id: \"ana#0001\" })).toBeUndefined();\n await tick();\n await tick();\n\n expect(gateway).toHaveBeenCalledTimes(1);\n expect(gateway.mock.calls[0][0].entries).toHaveLength(3);\n expect(titles.getTitle({ modelId: \"location\", id: \"baker#0001\" })).toBe(\"Baker\");\n expect(titles.getTitle({ modelId: \"coach\", id: \"ana#0001\" })).toBe(\"Ana\");\n });\n\n it(\"asks for a reference once, whichever revision it points at\", async () => {\n const { titles, gateway } = createContainer();\n\n titles.getTitle({ modelId: \"location\", id: \"baker#0001\" });\n await tick();\n await tick();\n titles.getTitle({ modelId: \"location\", id: \"baker#0001\" });\n titles.getTitle({ modelId: \"location\", id: \"baker#0003\" });\n await tick();\n\n expect(gateway).toHaveBeenCalledTimes(1);\n });\n\n it(\"settles a reference to a deleted entry as an empty title\", async () => {\n const { titles, gateway } = createContainer();\n\n titles.getTitle({ modelId: \"location\", id: \"gone#0001\" });\n await tick();\n await tick();\n\n expect(titles.getTitle({ modelId: \"location\", id: \"gone#0001\" })).toBe(\"\");\n expect(gateway).toHaveBeenCalledTimes(1);\n });\n\n it(\"asks again after a failed request\", async () => {\n const { titles, gateway } = createContainer();\n gateway.mockRejectedValueOnce(new Error(\"Network error\"));\n const consoleError = vi.spyOn(console, \"error\").mockImplementation(() => undefined);\n\n titles.getTitle({ modelId: \"location\", id: \"baker#0001\" });\n await tick();\n await tick();\n expect(titles.getTitle({ modelId: \"location\", id: \"baker#0001\" })).toBeUndefined();\n await tick();\n await tick();\n\n expect(gateway).toHaveBeenCalledTimes(2);\n expect(titles.getTitle({ modelId: \"location\", id: \"baker#0001\" })).toBe(\"Baker\");\n consoleError.mockRestore();\n });\n\n it(\"keeps titles of entries the reference inputs load, without a request of its own\", async () => {\n const { container, titles, gateway } = createContainer();\n\n await container.resolve(GetContentEntriesUseCase).execute({ entries: [] });\n await container.resolve(SearchContentEntriesUseCase).execute({ modelIds: [\"location\"] });\n\n expect(titles.getTitle({ modelId: \"location\", id: \"baker#0001\" })).toBe(\"Baker\");\n expect(titles.getTitle({ modelId: \"location\", id: \"lincoln#0001\" })).toBe(\"Lincoln Park\");\n await tick();\n expect(gateway).not.toHaveBeenCalled();\n });\n});\n"],"names":["createEntry","modelId","entryId","title","entries","createContainer","gateway","vi","refs","e","r","container","Container","GetContentEntriesGateway","GetContentEntriesUseCase","SearchContentEntriesUseCase","ReferenceTitlesFeature","Abstraction","tick","Promise","resolve","setTimeout","describe","it","titles","expect","Error","consoleError","console","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GetContentEntriesUseCase, type IGetContentEntriesUseCaseParams } from "../getContentEntries/abstractions.js";
|
|
2
|
+
import { SearchContentEntriesUseCase, type ISearchContentEntriesUseCaseParams } from "../searchContentEntries/abstractions.js";
|
|
3
|
+
import { ReferenceTitles } from "./abstractions.js";
|
|
4
|
+
declare class RememberResolvedTitles implements GetContentEntriesUseCase.Interface {
|
|
5
|
+
private titles;
|
|
6
|
+
private decoratee;
|
|
7
|
+
constructor(titles: ReferenceTitles.Interface, decoratee: GetContentEntriesUseCase.Interface);
|
|
8
|
+
execute(params: IGetContentEntriesUseCaseParams): Promise<import("../getContentEntries/abstractions.js").IGetContentEntriesUseCaseResult>;
|
|
9
|
+
}
|
|
10
|
+
declare class RememberSearchedTitles implements SearchContentEntriesUseCase.Interface {
|
|
11
|
+
private titles;
|
|
12
|
+
private decoratee;
|
|
13
|
+
constructor(titles: ReferenceTitles.Interface, decoratee: SearchContentEntriesUseCase.Interface);
|
|
14
|
+
execute(params: ISearchContentEntriesUseCaseParams): Promise<import("../searchContentEntries/abstractions.js").ISearchContentEntriesUseCaseResult>;
|
|
15
|
+
}
|
|
16
|
+
export declare const RememberResolvedTitlesDecorator: typeof RememberResolvedTitles & {
|
|
17
|
+
__abstraction: import("@webiny/di").Abstraction<import("../getContentEntries/abstractions.js").IGetContentEntriesUseCase>;
|
|
18
|
+
};
|
|
19
|
+
export declare const RememberSearchedTitlesDecorator: typeof RememberSearchedTitles & {
|
|
20
|
+
__abstraction: import("@webiny/di").Abstraction<import("../searchContentEntries/abstractions.js").ISearchContentEntriesUseCase>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GetContentEntriesUseCase } from "../getContentEntries/abstractions.js";
|
|
2
|
+
import { SearchContentEntriesUseCase } from "../searchContentEntries/abstractions.js";
|
|
3
|
+
import { ReferenceTitles } from "./abstractions.js";
|
|
4
|
+
class RememberResolvedTitles {
|
|
5
|
+
constructor(titles, decoratee){
|
|
6
|
+
this.titles = titles;
|
|
7
|
+
this.decoratee = decoratee;
|
|
8
|
+
}
|
|
9
|
+
async execute(params) {
|
|
10
|
+
const result = await this.decoratee.execute(params);
|
|
11
|
+
this.titles.remember(result.latest);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
class RememberSearchedTitles {
|
|
16
|
+
constructor(titles, decoratee){
|
|
17
|
+
this.titles = titles;
|
|
18
|
+
this.decoratee = decoratee;
|
|
19
|
+
}
|
|
20
|
+
async execute(params) {
|
|
21
|
+
const result = await this.decoratee.execute(params);
|
|
22
|
+
this.titles.remember(result.data);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const RememberResolvedTitlesDecorator = GetContentEntriesUseCase.createDecorator({
|
|
27
|
+
decorator: RememberResolvedTitles,
|
|
28
|
+
dependencies: [
|
|
29
|
+
ReferenceTitles
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
const RememberSearchedTitlesDecorator = SearchContentEntriesUseCase.createDecorator({
|
|
33
|
+
decorator: RememberSearchedTitles,
|
|
34
|
+
dependencies: [
|
|
35
|
+
ReferenceTitles
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
export { RememberResolvedTitlesDecorator, RememberSearchedTitlesDecorator };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=RememberTitlesDecorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/contentEntry/referenceTitles/RememberTitlesDecorators.js","sources":["../../../../src/features/contentEntry/referenceTitles/RememberTitlesDecorators.ts"],"sourcesContent":["import {\n GetContentEntriesUseCase,\n type IGetContentEntriesUseCaseParams\n} from \"../getContentEntries/abstractions.js\";\nimport {\n SearchContentEntriesUseCase,\n type ISearchContentEntriesUseCaseParams\n} from \"../searchContentEntries/abstractions.js\";\nimport { ReferenceTitles } from \"./abstractions.js\";\n\n// The reference inputs already load the entries they show. Keeping those titles means an\n// accordion title picks up a newly selected reference without a request of its own.\n\nclass RememberResolvedTitles implements GetContentEntriesUseCase.Interface {\n constructor(\n private titles: ReferenceTitles.Interface,\n private decoratee: GetContentEntriesUseCase.Interface\n ) {}\n\n async execute(params: IGetContentEntriesUseCaseParams) {\n const result = await this.decoratee.execute(params);\n this.titles.remember(result.latest);\n return result;\n }\n}\n\nclass RememberSearchedTitles implements SearchContentEntriesUseCase.Interface {\n constructor(\n private titles: ReferenceTitles.Interface,\n private decoratee: SearchContentEntriesUseCase.Interface\n ) {}\n\n async execute(params: ISearchContentEntriesUseCaseParams) {\n const result = await this.decoratee.execute(params);\n this.titles.remember(result.data);\n return result;\n }\n}\n\nexport const RememberResolvedTitlesDecorator = GetContentEntriesUseCase.createDecorator({\n decorator: RememberResolvedTitles,\n dependencies: [ReferenceTitles]\n});\n\nexport const RememberSearchedTitlesDecorator = SearchContentEntriesUseCase.createDecorator({\n decorator: RememberSearchedTitles,\n dependencies: [ReferenceTitles]\n});\n"],"names":["RememberResolvedTitles","titles","decoratee","params","result","RememberSearchedTitles","RememberResolvedTitlesDecorator","GetContentEntriesUseCase","ReferenceTitles","RememberSearchedTitlesDecorator","SearchContentEntriesUseCase"],"mappings":";;;AAaA,MAAMA;IACF,YACYC,MAAiC,EACjCC,SAA6C,CACvD;aAFUD,MAAM,GAANA;aACAC,SAAS,GAATA;IACT;IAEH,MAAM,QAAQC,MAAuC,EAAE;QACnD,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACD;QAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAACC,OAAO,MAAM;QAClC,OAAOA;IACX;AACJ;AAEA,MAAMC;IACF,YACYJ,MAAiC,EACjCC,SAAgD,CAC1D;aAFUD,MAAM,GAANA;aACAC,SAAS,GAATA;IACT;IAEH,MAAM,QAAQC,MAA0C,EAAE;QACtD,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACD;QAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAACC,OAAO,IAAI;QAChC,OAAOA;IACX;AACJ;AAEO,MAAME,kCAAkCC,yBAAyB,eAAe,CAAC;IACpF,WAAWP;IACX,cAAc;QAACQ;KAAgB;AACnC;AAEO,MAAMC,kCAAkCC,4BAA4B,eAAe,CAAC;IACvF,WAAWL;IACX,cAAc;QAACG;KAAgB;AACnC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CmsReferenceEntry, CmsReferenceValue } from "../refTypes.js";
|
|
2
|
+
export interface IReferenceTitles {
|
|
3
|
+
/**
|
|
4
|
+
* The title of the referenced entry, or `undefined` until it is known. Observable: an unknown
|
|
5
|
+
* reference is queued and loaded in one request with every other reference asked for in the
|
|
6
|
+
* same tick, so a list of fifty accordion items costs one round trip. A reference to an entry
|
|
7
|
+
* that no longer exists settles as an empty string.
|
|
8
|
+
*/
|
|
9
|
+
getTitle(ref: CmsReferenceValue): string | undefined;
|
|
10
|
+
/** Record the titles of entries loaded elsewhere, so they are never fetched again. */
|
|
11
|
+
remember(entries: CmsReferenceEntry[]): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const ReferenceTitles: import("@webiny/di").Abstraction<IReferenceTitles>;
|
|
14
|
+
export declare namespace ReferenceTitles {
|
|
15
|
+
type Interface = IReferenceTitles;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/contentEntry/referenceTitles/abstractions.js","sources":["../../../../src/features/contentEntry/referenceTitles/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { CmsReferenceEntry, CmsReferenceValue } from \"../refTypes.js\";\n\nexport interface IReferenceTitles {\n /**\n * The title of the referenced entry, or `undefined` until it is known. Observable: an unknown\n * reference is queued and loaded in one request with every other reference asked for in the\n * same tick, so a list of fifty accordion items costs one round trip. A reference to an entry\n * that no longer exists settles as an empty string.\n */\n getTitle(ref: CmsReferenceValue): string | undefined;\n /** Record the titles of entries loaded elsewhere, so they are never fetched again. */\n remember(entries: CmsReferenceEntry[]): void;\n}\n\nexport const ReferenceTitles = createAbstraction<IReferenceTitles>(\"CmsReferenceTitles\");\n\nexport namespace ReferenceTitles {\n export type Interface = IReferenceTitles;\n}\n"],"names":["ReferenceTitles","createAbstraction"],"mappings":";AAeO,MAAMA,kBAAkBC,kBAAoC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/admin";
|
|
2
|
+
import { ReferenceTitles } from "./abstractions.js";
|
|
3
|
+
import { ReferenceTitles as external_ReferenceTitles_js_ReferenceTitles } from "./ReferenceTitles.js";
|
|
4
|
+
import { RememberResolvedTitlesDecorator, RememberSearchedTitlesDecorator } from "./RememberTitlesDecorators.js";
|
|
5
|
+
const ReferenceTitlesFeature = createFeature({
|
|
6
|
+
name: "CmsContentEntry/ReferenceTitles",
|
|
7
|
+
register (container) {
|
|
8
|
+
container.register(external_ReferenceTitles_js_ReferenceTitles).inSingletonScope();
|
|
9
|
+
container.registerDecorator(RememberResolvedTitlesDecorator);
|
|
10
|
+
container.registerDecorator(RememberSearchedTitlesDecorator);
|
|
11
|
+
},
|
|
12
|
+
resolve (container) {
|
|
13
|
+
return {
|
|
14
|
+
referenceTitles: container.resolve(ReferenceTitles)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
export { ReferenceTitlesFeature };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/contentEntry/referenceTitles/feature.js","sources":["../../../../src/features/contentEntry/referenceTitles/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { ReferenceTitles as Abstraction } from \"./abstractions.js\";\nimport { ReferenceTitles } from \"./ReferenceTitles.js\";\nimport {\n RememberResolvedTitlesDecorator,\n RememberSearchedTitlesDecorator\n} from \"./RememberTitlesDecorators.js\";\n\nexport const ReferenceTitlesFeature = createFeature({\n name: \"CmsContentEntry/ReferenceTitles\",\n register(container) {\n container.register(ReferenceTitles).inSingletonScope();\n container.registerDecorator(RememberResolvedTitlesDecorator);\n container.registerDecorator(RememberSearchedTitlesDecorator);\n },\n resolve(container) {\n return {\n referenceTitles: container.resolve(Abstraction)\n };\n }\n});\n"],"names":["ReferenceTitlesFeature","createFeature","container","ReferenceTitles","RememberResolvedTitlesDecorator","RememberSearchedTitlesDecorator","Abstraction"],"mappings":";;;;AAQO,MAAMA,yBAAyBC,cAAc;IAChD,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC,6CAAiB,gBAAgB;QACpDD,UAAU,iBAAiB,CAACE;QAC5BF,UAAU,iBAAiB,CAACG;IAChC;IACA,SAAQH,SAAS;QACb,OAAO;YACH,iBAAiBA,UAAU,OAAO,CAACI;QACvC;IACJ;AACJ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FieldValueFormatter, type IFieldVM } from "@webiny/app-admin/features/formModel/abstractions.js";
|
|
2
|
+
import { ReferenceTitles } from "../../features/contentEntry/referenceTitles/abstractions.js";
|
|
3
|
+
declare class CmsRefFieldValueFormatterImpl implements FieldValueFormatter.Interface {
|
|
4
|
+
private titles;
|
|
5
|
+
readonly type = "ref";
|
|
6
|
+
constructor(titles: ReferenceTitles.Interface);
|
|
7
|
+
format(field: IFieldVM): string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare const CmsRefFieldValueFormatter: typeof CmsRefFieldValueFormatterImpl & {
|
|
10
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/app-admin/features/formModel").IFieldValueFormatter>;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FieldValueFormatter } from "@webiny/app-admin/features/formModel/abstractions.js";
|
|
2
|
+
import { ReferenceTitles } from "../contentEntry/referenceTitles/abstractions.js";
|
|
3
|
+
function isReference(value) {
|
|
4
|
+
const ref = value;
|
|
5
|
+
return "string" == typeof ref?.id && "string" == typeof ref?.modelId;
|
|
6
|
+
}
|
|
7
|
+
class CmsRefFieldValueFormatterImpl {
|
|
8
|
+
constructor(titles){
|
|
9
|
+
this.titles = titles;
|
|
10
|
+
this.type = "ref";
|
|
11
|
+
}
|
|
12
|
+
format(field) {
|
|
13
|
+
const values = Array.isArray(field.value) ? field.value : [
|
|
14
|
+
field.value
|
|
15
|
+
];
|
|
16
|
+
const titles = values.filter(isReference).map((ref)=>this.titles.getTitle(ref));
|
|
17
|
+
if (titles.some((title)=>void 0 === title)) return;
|
|
18
|
+
return titles.filter(Boolean).join(", ");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const CmsRefFieldValueFormatter = FieldValueFormatter.createImplementation({
|
|
22
|
+
implementation: CmsRefFieldValueFormatterImpl,
|
|
23
|
+
dependencies: [
|
|
24
|
+
ReferenceTitles
|
|
25
|
+
]
|
|
26
|
+
});
|
|
27
|
+
export { CmsRefFieldValueFormatter };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=CmsRefFieldValueFormatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/formModel/CmsRefFieldValueFormatter.js","sources":["../../../src/features/formModel/CmsRefFieldValueFormatter.ts"],"sourcesContent":["import {\n FieldValueFormatter,\n type IFieldVM\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { ReferenceTitles } from \"~/features/contentEntry/referenceTitles/abstractions.js\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\n\nfunction isReference(value: unknown): value is CmsReferenceValue {\n const ref = value as CmsReferenceValue | null;\n return typeof ref?.id === \"string\" && typeof ref?.modelId === \"string\";\n}\n\nclass CmsRefFieldValueFormatterImpl implements FieldValueFormatter.Interface {\n readonly type = \"ref\";\n\n constructor(private titles: ReferenceTitles.Interface) {}\n\n format(field: IFieldVM): string | undefined {\n const values = Array.isArray(field.value) ? field.value : [field.value];\n const titles = values.filter(isReference).map(ref => this.titles.getTitle(ref));\n // Show nothing until every title is in, rather than a list that fills in one at a time.\n if (titles.some(title => title === undefined)) {\n return undefined;\n }\n return titles.filter(Boolean).join(\", \");\n }\n}\n\nexport const CmsRefFieldValueFormatter = FieldValueFormatter.createImplementation({\n implementation: CmsRefFieldValueFormatterImpl,\n dependencies: [ReferenceTitles]\n});\n"],"names":["isReference","value","ref","CmsRefFieldValueFormatterImpl","titles","field","values","Array","title","undefined","Boolean","CmsRefFieldValueFormatter","FieldValueFormatter","ReferenceTitles"],"mappings":";;AAOA,SAASA,YAAYC,KAAc;IAC/B,MAAMC,MAAMD;IACZ,OAAO,AAAmB,YAAnB,OAAOC,KAAK,MAAmB,AAAwB,YAAxB,OAAOA,KAAK;AACtD;AAEA,MAAMC;IAGF,YAAoBC,MAAiC,CAAE;aAAnCA,MAAM,GAANA;aAFX,IAAI,GAAG;IAEwC;IAExD,OAAOC,KAAe,EAAsB;QACxC,MAAMC,SAASC,MAAM,OAAO,CAACF,MAAM,KAAK,IAAIA,MAAM,KAAK,GAAG;YAACA,MAAM,KAAK;SAAC;QACvE,MAAMD,SAASE,OAAO,MAAM,CAACN,aAAa,GAAG,CAACE,CAAAA,MAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAACA;QAE1E,IAAIE,OAAO,IAAI,CAACI,CAAAA,QAASA,AAAUC,WAAVD,QACrB;QAEJ,OAAOJ,OAAO,MAAM,CAACM,SAAS,IAAI,CAAC;IACvC;AACJ;AAEO,MAAMC,4BAA4BC,oBAAoB,oBAAoB,CAAC;IAC9E,gBAAgBT;IAChB,cAAc;QAACU;KAAgB;AACnC"}
|
|
@@ -2,11 +2,13 @@ import { createFeature } from "@webiny/feature/admin";
|
|
|
2
2
|
import { CmsFormModelBuilder } from "./abstractions.js";
|
|
3
3
|
import { CmsFormModelBuilder as external_CmsFormModelBuilder_js_CmsFormModelBuilder } from "./CmsFormModelBuilder.js";
|
|
4
4
|
import { CmsRefFieldType } from "./CmsRefFieldType.js";
|
|
5
|
+
import { CmsRefFieldValueFormatter } from "./CmsRefFieldValueFormatter.js";
|
|
5
6
|
const CmsFormModelFeature = createFeature({
|
|
6
7
|
name: "CmsFormModel",
|
|
7
8
|
register (container) {
|
|
8
9
|
container.register(external_CmsFormModelBuilder_js_CmsFormModelBuilder).inSingletonScope();
|
|
9
10
|
container.register(CmsRefFieldType);
|
|
11
|
+
container.register(CmsRefFieldValueFormatter);
|
|
10
12
|
},
|
|
11
13
|
resolve (container) {
|
|
12
14
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/formModel/feature.js","sources":["../../../src/features/formModel/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { CmsFormModelBuilder as CmsFormModelBuilderAbstraction } from \"./abstractions.js\";\nimport { CmsFormModelBuilder } from \"./CmsFormModelBuilder.js\";\nimport { CmsRefFieldType } from \"./CmsRefFieldType.js\";\n\nexport const CmsFormModelFeature = createFeature({\n name: \"CmsFormModel\",\n register(container) {\n container.register(CmsFormModelBuilder).inSingletonScope();\n container.register(CmsRefFieldType);\n },\n resolve(container) {\n return {\n builder: container.resolve(CmsFormModelBuilderAbstraction)\n };\n }\n});\n"],"names":["CmsFormModelFeature","createFeature","container","CmsFormModelBuilder","CmsRefFieldType","CmsFormModelBuilderAbstraction"],"mappings":"
|
|
1
|
+
{"version":3,"file":"features/formModel/feature.js","sources":["../../../src/features/formModel/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { CmsFormModelBuilder as CmsFormModelBuilderAbstraction } from \"./abstractions.js\";\nimport { CmsFormModelBuilder } from \"./CmsFormModelBuilder.js\";\nimport { CmsRefFieldType } from \"./CmsRefFieldType.js\";\nimport { CmsRefFieldValueFormatter } from \"./CmsRefFieldValueFormatter.js\";\n\nexport const CmsFormModelFeature = createFeature({\n name: \"CmsFormModel\",\n register(container) {\n container.register(CmsFormModelBuilder).inSingletonScope();\n container.register(CmsRefFieldType);\n container.register(CmsRefFieldValueFormatter);\n },\n resolve(container) {\n return {\n builder: container.resolve(CmsFormModelBuilderAbstraction)\n };\n }\n});\n"],"names":["CmsFormModelFeature","createFeature","container","CmsFormModelBuilder","CmsRefFieldType","CmsRefFieldValueFormatter","CmsFormModelBuilderAbstraction"],"mappings":";;;;;AAMO,MAAMA,sBAAsBC,cAAc;IAC7C,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC,qDAAqB,gBAAgB;QACxDD,UAAU,QAAQ,CAACE;QACnBF,UAAU,QAAQ,CAACG;IACvB;IACA,SAAQH,SAAS;QACb,OAAO;YACH,SAASA,UAAU,OAAO,CAACI;QAC/B;IACJ;AACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/app-headless-cms",
|
|
3
|
-
"version": "6.5.0-beta.
|
|
3
|
+
"version": "6.5.0-beta.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -28,25 +28,25 @@
|
|
|
28
28
|
"@fortawesome/react-fontawesome": "3.5.0",
|
|
29
29
|
"@lexical/utils": "0.52.0",
|
|
30
30
|
"@types/react": "18.3.31",
|
|
31
|
-
"@webiny/admin-ui": "6.5.0-beta.
|
|
32
|
-
"@webiny/app": "6.5.0-beta.
|
|
33
|
-
"@webiny/app-aco": "6.5.0-beta.
|
|
34
|
-
"@webiny/app-admin": "6.5.0-beta.
|
|
35
|
-
"@webiny/app-graphql-playground": "6.5.0-beta.
|
|
36
|
-
"@webiny/app-headless-cms-common": "6.5.0-beta.
|
|
37
|
-
"@webiny/cms-sdk": "6.5.0-beta.
|
|
31
|
+
"@webiny/admin-ui": "6.5.0-beta.12",
|
|
32
|
+
"@webiny/app": "6.5.0-beta.12",
|
|
33
|
+
"@webiny/app-aco": "6.5.0-beta.12",
|
|
34
|
+
"@webiny/app-admin": "6.5.0-beta.12",
|
|
35
|
+
"@webiny/app-graphql-playground": "6.5.0-beta.12",
|
|
36
|
+
"@webiny/app-headless-cms-common": "6.5.0-beta.12",
|
|
37
|
+
"@webiny/cms-sdk": "6.5.0-beta.12",
|
|
38
38
|
"@webiny/di": "1.1.0",
|
|
39
|
-
"@webiny/feature": "6.5.0-beta.
|
|
40
|
-
"@webiny/form": "6.5.0-beta.
|
|
41
|
-
"@webiny/frontend-settings": "6.5.0-beta.
|
|
42
|
-
"@webiny/icons": "6.5.0-beta.
|
|
43
|
-
"@webiny/lexical-editor": "6.5.0-beta.
|
|
44
|
-
"@webiny/lexical-nodes": "6.5.0-beta.
|
|
45
|
-
"@webiny/plugins": "6.5.0-beta.
|
|
46
|
-
"@webiny/react-composition": "6.5.0-beta.
|
|
47
|
-
"@webiny/react-properties": "6.5.0-beta.
|
|
48
|
-
"@webiny/utils": "6.5.0-beta.
|
|
49
|
-
"@webiny/validation": "6.5.0-beta.
|
|
39
|
+
"@webiny/feature": "6.5.0-beta.12",
|
|
40
|
+
"@webiny/form": "6.5.0-beta.12",
|
|
41
|
+
"@webiny/frontend-settings": "6.5.0-beta.12",
|
|
42
|
+
"@webiny/icons": "6.5.0-beta.12",
|
|
43
|
+
"@webiny/lexical-editor": "6.5.0-beta.12",
|
|
44
|
+
"@webiny/lexical-nodes": "6.5.0-beta.12",
|
|
45
|
+
"@webiny/plugins": "6.5.0-beta.12",
|
|
46
|
+
"@webiny/react-composition": "6.5.0-beta.12",
|
|
47
|
+
"@webiny/react-properties": "6.5.0-beta.12",
|
|
48
|
+
"@webiny/utils": "6.5.0-beta.12",
|
|
49
|
+
"@webiny/validation": "6.5.0-beta.12",
|
|
50
50
|
"dot-prop-immutable": "2.1.1",
|
|
51
51
|
"graphql": "16.14.2",
|
|
52
52
|
"graphql-tag": "2.12.7",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"zod": "4.6.5"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@webiny/build-tools": "6.5.0-beta.
|
|
66
|
+
"@webiny/build-tools": "6.5.0-beta.12",
|
|
67
67
|
"rimraf": "6.1.3",
|
|
68
68
|
"typescript": "7.0.2",
|
|
69
69
|
"vitest": "5.0.3"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction, toJS } from \"mobx\";\nimport { FormModel, FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport type { CmsContentEntry, CmsModel } from \"~/types.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 { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n ContentEntryFormModelModifier,\n ContentEntryFormPresenter as Abstraction\n} from \"./abstractions.js\";\nimport { TRASH_ENTRY_DIALOG } from \"~/presentation/contentEntries/list/ContentEntriesPresenter.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { toFormErrors } from \"./toFormErrors.js\";\n\ninterface PublishEntryDialogData {\n revisionDescription: string;\n}\n\ninterface SaveEntryParams {\n model: CmsModel;\n entry: CmsContentEntry;\n data: Record<string, unknown>;\n skipValidation?: boolean;\n}\n\nexport const PUBLISH_ENTRY_DIALOG = \"publish-entry\";\n\nconst SAVING = \"Saving...\";\n\nclass ContentEntryFormPresenterImpl implements Abstraction.Interface {\n private entry: CmsContentEntry | null = null;\n private form: FormModel.Interface | null = null;\n private loading: string | null = null;\n private folderId: string | null = null;\n\n constructor(\n private readonly formModelFactory: FormModelFactory.Interface,\n private readonly cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private readonly confirmation: Confirmation.Interface,\n private readonly modelAccessor: CmsModelContext.Interface,\n private readonly getEntryUseCase: GetEntryUseCase.Interface,\n private readonly createEntryUseCase: CreateEntryUseCase.Interface,\n private readonly createRevisionFromUseCase: CreateRevisionFromUseCase.Interface,\n private readonly updateEntryUseCase: UpdateEntryUseCase.Interface,\n private readonly publishEntryUseCase: PublishEntryUseCase.Interface,\n private readonly unpublishEntryUseCase: UnpublishEntryUseCase.Interface,\n private readonly deleteEntryUseCase: DeleteEntryUseCase.Interface,\n private readonly updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface,\n private readonly formModelModifiers: ContentEntryFormModelModifier.Interface[]\n ) {\n makeAutoObservable<\n ContentEntryFormPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"confirmation\"\n | \"modelAccessor\"\n | \"getEntryUseCase\"\n | \"createEntryUseCase\"\n | \"updateEntryUseCase\"\n | \"publishEntryUseCase\"\n | \"unpublishEntryUseCase\"\n | \"deleteEntryUseCase\"\n | \"updateRevisionDescriptionUseCase\"\n | \"formModelModifiers\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n confirmation: false,\n modelAccessor: false,\n getEntryUseCase: false,\n createEntryUseCase: false,\n updateEntryUseCase: false,\n publishEntryUseCase: false,\n unpublishEntryUseCase: false,\n deleteEntryUseCase: false,\n updateRevisionDescriptionUseCase: false,\n formModelModifiers: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): Abstraction.ViewModel {\n const entry = this.entry;\n const meta = entry?.meta;\n const isLocked = meta?.locked ?? false;\n const status = meta?.status;\n /**\n * Can save if the entry is locked and published/unpublished - it will trigger create new revision.\n * Or if the entry is not locked and the form is not null (meaning we are creating a new entry or editing an existing one).\n */\n const canSave =\n (isLocked && (status === \"published\" || status === \"unpublished\")) ||\n (!isLocked && this.form !== null);\n\n return {\n loading: this.loading,\n model: this.model,\n entry: toJS(this.entry),\n form: this.form?.vm ?? null,\n canSave,\n status,\n canCreateNewRevision:\n !!status && canSave && [\"published\", \"unpublished\"].includes(status),\n canPublish: this.entry !== null && status !== \"published\",\n canUnpublish: this.entry !== null && status === \"published\",\n canDelete: this.entry !== null,\n isNewEntry: this.entry === null && this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async saveRevision(options?: { skipValidation?: boolean }): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n /*\n * Saving is async, and the form can be reset or replaced by another entry before it\n * finishes. Keep hold of the form and model being saved, and only write the result back\n * while that form is still the one on screen.\n */\n const form = this.form;\n const model = this.model;\n const isCurrent = () => this.form === form && this.model.modelId === model.modelId;\n\n const skipValidation = options?.skipValidation ?? true;\n const data = await form.submit({ skipValidation });\n\n /*\n * Validation is async too. If another entry was opened in the meantime, `this.entry`\n * already points at it, and saving now would write this form's values onto that entry.\n */\n if (!data || !isCurrent()) {\n return false;\n }\n\n // Read the write target once, before the first await, so the write stays on this entry.\n const entry = this.entry;\n const folderId = this.folderId;\n\n runInAction(() => {\n this.loading = SAVING;\n });\n\n try {\n let savedEntry: CmsContentEntry;\n if (entry) {\n /**\n * In case the entry already exists, and it is NOT locked, we will update the existing revision.\n * If it is locked, we will create a new revision from the existing one, and then update that new revision.\n */\n savedEntry = await this.saveEntry({ model, entry, data, skipValidation });\n } else {\n const createData: Record<string, unknown> = { values: data };\n if (folderId) {\n createData.wbyAco_location = { folderId };\n }\n\n savedEntry = await this.createEntryUseCase.execute({\n model,\n data: createData,\n options: { skipValidation }\n });\n }\n\n /*\n * The write went to the right entry, but another form is on screen now. Leave it\n * alone and return false, so callers don't select, toast or publish the entry that\n * is open now instead of the one that was saved.\n */\n if (!isCurrent()) {\n return false;\n }\n\n runInAction(() => {\n this.entry = savedEntry;\n form.setData(savedEntry.values);\n form.reset();\n });\n\n return true;\n } catch (error) {\n if (isCurrent()) {\n form.setErrors(toFormErrors(error, model));\n }\n return false;\n } finally {\n runInAction(() => {\n // Opening another entry sets its own loading message. Don't clear that one.\n if (this.loading === SAVING) {\n this.loading = null;\n }\n });\n }\n }\n\n async publishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const result = await this.confirmation.confirm<PublishEntryDialogData>(\n PUBLISH_ENTRY_DIALOG,\n { entry: this.entry }\n );\n\n if (result === false) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Publishing...\";\n });\n\n try {\n if (result.revisionDescription) {\n await this.updateRevisionDescriptionUseCase.execute({\n model: this.model,\n id: this.entry.id,\n revisionDescription: result.revisionDescription\n });\n }\n\n const entry = await this.publishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async unpublishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n this.loading = \"Unpublishing...\";\n\n try {\n const entry = await this.unpublishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async deleteEntry(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const model = this.model;\n const entry = this.entry;\n\n return this.confirmation.confirm(\n TRASH_ENTRY_DIALOG,\n { entryId: entry.entryId },\n async () => {\n try {\n await this.deleteEntryUseCase.execute({\n model,\n id: entry.entryId\n });\n\n runInAction(() => {\n this.entry = null;\n this.form = null;\n });\n\n return true;\n } catch {\n return false;\n }\n }\n );\n }\n\n async loadRevision(id: string): Promise<void> {\n this.loading = \"Loading entry...\";\n\n try {\n const entry = await this.getEntryUseCase.execute({\n model: this.model,\n id\n });\n\n runInAction(() => {\n this.entry = entry;\n this.buildAndPopulateForm(entry);\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n setFolderId(folderId: string | null): void {\n this.folderId = folderId;\n }\n\n newEntry(initialValues?: Record<string, unknown>): void {\n this.entry = null;\n this.form = this.buildForm();\n if (initialValues) {\n this.form.setData(initialValues, { dirty: true });\n }\n }\n\n patchEntryMeta(meta: Partial<CmsContentEntry[\"meta\"]>): void {\n if (!this.entry) {\n return;\n }\n this.entry = {\n ...this.entry,\n meta: { ...this.entry.meta, ...meta }\n };\n }\n\n reset(): void {\n this.form = null;\n this.entry = null;\n }\n\n private buildForm(): FormModel.Interface {\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n const form = this.formModelFactory.create(formConfig);\n for (const modifier of this.formModelModifiers) {\n modifier.modifyForm(form, this.model);\n }\n return form;\n }\n\n private buildAndPopulateForm(entry: CmsContentEntry): void {\n this.form = this.buildForm();\n this.form.setData(entry.values);\n this.form.reset();\n }\n /**\n * Depending on whether the entry is locked or not, this method will either create a new revision from the existing one and update it, or simply update the existing revision.\n */\n private async saveEntry(params: SaveEntryParams): Promise<CmsContentEntry> {\n const { model, entry, data, skipValidation } = params;\n\n if (entry.meta.locked) {\n return await this.createRevisionFromUseCase.execute({\n model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n return await this.updateEntryUseCase.execute({\n model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n}\n\nexport const ContentEntryFormPresenter = Abstraction.createImplementation({\n implementation: ContentEntryFormPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n Confirmation,\n CmsModelContext,\n GetEntryUseCase,\n CreateEntryUseCase,\n CreateRevisionFromUseCase,\n UpdateEntryUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase,\n [ContentEntryFormModelModifier, { multiple: true }]\n ]\n});\n"],"names":["PUBLISH_ENTRY_DIALOG","SAVING","ContentEntryFormPresenterImpl","formModelFactory","cmsFormModelBuilder","confirmation","modelAccessor","getEntryUseCase","createEntryUseCase","createRevisionFromUseCase","updateEntryUseCase","publishEntryUseCase","unpublishEntryUseCase","deleteEntryUseCase","updateRevisionDescriptionUseCase","formModelModifiers","makeAutoObservable","computed","entry","meta","isLocked","status","canSave","toJS","options","form","model","isCurrent","skipValidation","data","folderId","runInAction","savedEntry","createData","error","toFormErrors","result","TRASH_ENTRY_DIALOG","id","initialValues","formConfig","modifier","params","ContentEntryFormPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","Confirmation","CmsModelContext","GetEntryUseCase","CreateEntryUseCase","CreateRevisionFromUseCase","UpdateEntryUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormModelModifier"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction, toJS } from \"mobx\";\nimport { FormModel, FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport type { CmsContentEntry, CmsModel } from \"~/types.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 { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n ContentEntryFormModelModifier,\n ContentEntryFormPresenter as Abstraction\n} from \"./abstractions.js\";\nimport { TRASH_ENTRY_DIALOG } from \"~/presentation/contentEntries/list/ContentEntriesPresenter.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { toFormErrors } from \"./toFormErrors.js\";\n\ninterface PublishEntryDialogData {\n revisionDescription: string;\n}\n\ninterface SaveEntryParams {\n model: CmsModel;\n entry: CmsContentEntry;\n data: Record<string, unknown>;\n skipValidation?: boolean;\n}\n\nexport const PUBLISH_ENTRY_DIALOG = \"publish-entry\";\n\nconst SAVING = \"Saving...\";\n\nclass ContentEntryFormPresenterImpl implements Abstraction.Interface {\n private entry: CmsContentEntry | null = null;\n private form: FormModel.Interface | null = null;\n private loading: string | null = null;\n private folderId: string | null = null;\n\n constructor(\n private readonly formModelFactory: FormModelFactory.Interface,\n private readonly cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private readonly confirmation: Confirmation.Interface,\n private readonly modelAccessor: CmsModelContext.Interface,\n private readonly getEntryUseCase: GetEntryUseCase.Interface,\n private readonly createEntryUseCase: CreateEntryUseCase.Interface,\n private readonly createRevisionFromUseCase: CreateRevisionFromUseCase.Interface,\n private readonly updateEntryUseCase: UpdateEntryUseCase.Interface,\n private readonly publishEntryUseCase: PublishEntryUseCase.Interface,\n private readonly unpublishEntryUseCase: UnpublishEntryUseCase.Interface,\n private readonly deleteEntryUseCase: DeleteEntryUseCase.Interface,\n private readonly updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface,\n private readonly formModelModifiers: ContentEntryFormModelModifier.Interface[]\n ) {\n makeAutoObservable<\n ContentEntryFormPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"confirmation\"\n | \"modelAccessor\"\n | \"getEntryUseCase\"\n | \"createEntryUseCase\"\n | \"updateEntryUseCase\"\n | \"publishEntryUseCase\"\n | \"unpublishEntryUseCase\"\n | \"deleteEntryUseCase\"\n | \"updateRevisionDescriptionUseCase\"\n | \"formModelModifiers\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n confirmation: false,\n modelAccessor: false,\n getEntryUseCase: false,\n createEntryUseCase: false,\n updateEntryUseCase: false,\n publishEntryUseCase: false,\n unpublishEntryUseCase: false,\n deleteEntryUseCase: false,\n updateRevisionDescriptionUseCase: false,\n formModelModifiers: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): Abstraction.ViewModel {\n const entry = this.entry;\n const meta = entry?.meta;\n const isLocked = meta?.locked ?? false;\n const status = meta?.status;\n /**\n * Can save if the entry is locked and published/unpublished - it will trigger create new revision.\n * Or if the entry is not locked and the form is not null (meaning we are creating a new entry or editing an existing one).\n */\n const canSave =\n (isLocked && (status === \"published\" || status === \"unpublished\")) ||\n (!isLocked && this.form !== null);\n\n return {\n loading: this.loading,\n model: this.model,\n entry: toJS(this.entry),\n form: this.form?.vm ?? null,\n canSave,\n status,\n canCreateNewRevision:\n !!status && canSave && [\"published\", \"unpublished\"].includes(status),\n canPublish: this.entry !== null && status !== \"published\",\n canUnpublish: this.entry !== null && status === \"published\",\n canDelete: this.entry !== null,\n isNewEntry: this.entry === null && this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async saveRevision(options?: { skipValidation?: boolean }): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n /*\n * Saving is async, and the form can be reset or replaced by another entry before it\n * finishes. Keep hold of the form and model being saved, and only write the result back\n * while that form is still the one on screen.\n */\n const form = this.form;\n const model = this.model;\n const isCurrent = () => this.form === form && this.model.modelId === model.modelId;\n\n const skipValidation = options?.skipValidation ?? true;\n const data = await form.submit({ skipValidation });\n\n /*\n * Validation is async too. If another entry was opened in the meantime, `this.entry`\n * already points at it, and saving now would write this form's values onto that entry.\n */\n if (!data || !isCurrent()) {\n return false;\n }\n\n // Read the write target once, before the first await, so the write stays on this entry.\n const entry = this.entry;\n const folderId = this.folderId;\n\n runInAction(() => {\n this.loading = SAVING;\n });\n\n try {\n let savedEntry: CmsContentEntry;\n if (entry) {\n /**\n * In case the entry already exists, and it is NOT locked, we will update the existing revision.\n * If it is locked, we will create a new revision from the existing one, and then update that new revision.\n */\n savedEntry = await this.saveEntry({ model, entry, data, skipValidation });\n } else {\n const createData: Record<string, unknown> = { values: data };\n if (folderId) {\n createData.wbyAco_location = { folderId };\n }\n\n savedEntry = await this.createEntryUseCase.execute({\n model,\n data: createData,\n options: { skipValidation }\n });\n }\n\n /*\n * The write went to the right entry, but another form is on screen now. Leave it\n * alone and return false, so callers don't select, toast or publish the entry that\n * is open now instead of the one that was saved.\n */\n if (!isCurrent()) {\n return false;\n }\n\n runInAction(() => {\n this.entry = savedEntry;\n form.setData(savedEntry.values);\n form.reset();\n });\n\n return true;\n } catch (error) {\n if (isCurrent()) {\n form.setErrors(toFormErrors(error, model));\n }\n return false;\n } finally {\n runInAction(() => {\n // Opening another entry sets its own loading message. Don't clear that one.\n if (this.loading === SAVING) {\n this.loading = null;\n }\n });\n }\n }\n\n async publishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const result = await this.confirmation.confirm<PublishEntryDialogData>(\n PUBLISH_ENTRY_DIALOG,\n { entry: this.entry }\n );\n\n if (result === false) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Publishing...\";\n });\n\n try {\n if (result.revisionDescription) {\n await this.updateRevisionDescriptionUseCase.execute({\n model: this.model,\n id: this.entry.id,\n revisionDescription: result.revisionDescription\n });\n }\n\n const entry = await this.publishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async unpublishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n this.loading = \"Unpublishing...\";\n\n try {\n const entry = await this.unpublishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async deleteEntry(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const model = this.model;\n const entry = this.entry;\n\n return this.confirmation.confirm(\n TRASH_ENTRY_DIALOG,\n { entryId: entry.entryId },\n async () => {\n try {\n await this.deleteEntryUseCase.execute({\n model,\n id: entry.entryId\n });\n\n runInAction(() => {\n this.entry = null;\n this.form = null;\n });\n\n return true;\n } catch {\n return false;\n }\n }\n );\n }\n\n async loadRevision(id: string): Promise<void> {\n this.loading = \"Loading entry...\";\n\n try {\n const entry = await this.getEntryUseCase.execute({\n model: this.model,\n id\n });\n\n runInAction(() => {\n this.entry = entry;\n this.buildAndPopulateForm(entry);\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n setFolderId(folderId: string | null): void {\n this.folderId = folderId;\n }\n\n newEntry(initialValues?: Record<string, unknown>): void {\n this.entry = null;\n this.form = this.buildForm();\n if (initialValues) {\n this.form.setData(initialValues, { dirty: true });\n }\n }\n\n patchEntryMeta(meta: Partial<CmsContentEntry[\"meta\"]>): void {\n if (!this.entry) {\n return;\n }\n this.entry = {\n ...this.entry,\n meta: { ...this.entry.meta, ...meta }\n };\n }\n\n reset(): void {\n this.form = null;\n this.entry = null;\n this.folderId = null;\n }\n\n private buildForm(): FormModel.Interface {\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n const form = this.formModelFactory.create(formConfig);\n for (const modifier of this.formModelModifiers) {\n modifier.modifyForm(form, this.model);\n }\n return form;\n }\n\n private buildAndPopulateForm(entry: CmsContentEntry): void {\n this.form = this.buildForm();\n this.form.setData(entry.values);\n this.form.reset();\n }\n /**\n * Depending on whether the entry is locked or not, this method will either create a new revision from the existing one and update it, or simply update the existing revision.\n */\n private async saveEntry(params: SaveEntryParams): Promise<CmsContentEntry> {\n const { model, entry, data, skipValidation } = params;\n\n if (entry.meta.locked) {\n return await this.createRevisionFromUseCase.execute({\n model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n return await this.updateEntryUseCase.execute({\n model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n}\n\nexport const ContentEntryFormPresenter = Abstraction.createImplementation({\n implementation: ContentEntryFormPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n Confirmation,\n CmsModelContext,\n GetEntryUseCase,\n CreateEntryUseCase,\n CreateRevisionFromUseCase,\n UpdateEntryUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase,\n [ContentEntryFormModelModifier, { multiple: true }]\n ]\n});\n"],"names":["PUBLISH_ENTRY_DIALOG","SAVING","ContentEntryFormPresenterImpl","formModelFactory","cmsFormModelBuilder","confirmation","modelAccessor","getEntryUseCase","createEntryUseCase","createRevisionFromUseCase","updateEntryUseCase","publishEntryUseCase","unpublishEntryUseCase","deleteEntryUseCase","updateRevisionDescriptionUseCase","formModelModifiers","makeAutoObservable","computed","entry","meta","isLocked","status","canSave","toJS","options","form","model","isCurrent","skipValidation","data","folderId","runInAction","savedEntry","createData","error","toFormErrors","result","TRASH_ENTRY_DIALOG","id","initialValues","formConfig","modifier","params","ContentEntryFormPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","Confirmation","CmsModelContext","GetEntryUseCase","CreateEntryUseCase","CreateRevisionFromUseCase","UpdateEntryUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormModelModifier"],"mappings":"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"}
|
|
@@ -117,6 +117,40 @@ describe("ContentEntryFormPresenter.saveRevision", ()=>{
|
|
|
117
117
|
await expect(saving).resolves.toBe(false);
|
|
118
118
|
expect(forms[0].setErrors).toHaveBeenCalledTimes(1);
|
|
119
119
|
});
|
|
120
|
+
it("creates a new entry in the folder it was started in", async ()=>{
|
|
121
|
+
const { presenter, create, createExecute } = setup();
|
|
122
|
+
presenter.setFolderId("folder-1");
|
|
123
|
+
presenter.newEntry();
|
|
124
|
+
const saving = presenter.saveRevision();
|
|
125
|
+
create.resolve(createEntry("A"));
|
|
126
|
+
await expect(saving).resolves.toBe(true);
|
|
127
|
+
expect(createExecute).toHaveBeenCalledWith(expect.objectContaining({
|
|
128
|
+
data: {
|
|
129
|
+
values: {
|
|
130
|
+
title: "Draft"
|
|
131
|
+
},
|
|
132
|
+
wbyAco_location: {
|
|
133
|
+
folderId: "folder-1"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
it("does not carry the folder over to an entry started after a reset", async ()=>{
|
|
139
|
+
const { presenter, create, createExecute } = setup();
|
|
140
|
+
presenter.setFolderId("folder-1");
|
|
141
|
+
presenter.reset();
|
|
142
|
+
presenter.newEntry();
|
|
143
|
+
const saving = presenter.saveRevision();
|
|
144
|
+
create.resolve(createEntry("A"));
|
|
145
|
+
await expect(saving).resolves.toBe(true);
|
|
146
|
+
expect(createExecute).toHaveBeenCalledWith(expect.objectContaining({
|
|
147
|
+
data: {
|
|
148
|
+
values: {
|
|
149
|
+
title: "Draft"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
120
154
|
it("returns false instead of throwing when the form was reset mid-save", async ()=>{
|
|
121
155
|
const { presenter, create, createExecute } = setup();
|
|
122
156
|
presenter.newEntry();
|
|
@@ -1 +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":";;;;;;;;;;;;;;;;AAkBA,MAAMA,iBAAiB,CAACC,aAAmC;QACvD,IAAI,CAAC;QACL,SAAS;QACT,QAAQC,GAAG,EAAE,CAAC,UAAaD,aAAaA,aAAa;gBAAE,OAAO;YAAQ;QACtE,SAASC,GAAG,EAAE;QACd,OAAOA,GAAG,EAAE;QACZ,WAAWA,GAAG,EAAE;IACpB;AAGA,MAAMC,WAAW;IACb,IAAIC;IACJ,IAAIC;IACJ,MAAMC,UAAU,IAAIC,QAAW,CAACC,KAAKC;QACjCL,UAAUI;QACVH,SAASI;IACb;IACA,OAAO;QAAEH;QAASF;QAASC;IAAO;AACtC;AAEA,MAAMK,cAAc,CAACC,UAAqB;QACtC,IAAI,GAAGA,QAAQ,KAAK,CAAC;QACrBA;QACA,MAAM;YAAE,QAAQ;YAAO,QAAQ;YAAS,OAAOA;QAAQ;QACvD,QAAQ;YAAE,OAAOA;QAAQ;IAC7B;AAEA,MAAMC,QAAQ;IACV,MAAMC,QAA6C,EAAE;IACrD,MAAMC,SAASX;IACf,MAAMY,SAASZ;IAEf,IAAIa;IAEJ,MAAMC,eAAe,IAAIC;IAEzB,MAAMC,gBAAgBjB,GAAG,EAAE,CAAC,IAAMY,OAAO,OAAO;IAChD,MAAMM,gBAAgBlB,GAAG,EAAE,CAAC,IAAMa,OAAO,OAAO;IAEhD,MAAMM,YAAY,IAAIC;IACtBD,UAAU,gBAAgB,CAACE,kBAAkB;QACzC,QAAQ;YACJ,MAAMC,OAAOxB,eAAegB;YAC5BA,iBAAiBS;YACjBZ,MAAM,IAAI,CAACW;YACX,OAAOA;QACX;IACJ;IACAH,UAAU,gBAAgB,CAACK,qBAAqB;QAC5C,OAAO,IAAO,EAAC;IACnB;IACAL,UAAU,gBAAgB,CAACM,iBAAiB;QACxC,UAAU,IAAO;gBAAE,SAAS;gBAAW,QAAQ,EAAE;YAAC;IACtD;IACAN,UAAU,gBAAgB,CAACO,oBAAoB;QAC3C,SAAST;IACb;IACAE,UAAU,gBAAgB,CAACQ,oBAAoB;QAC3C,SAAST;IACb;IACAC,UAAU,gBAAgB,CAACS,iBAAiB;QACxC,SAAS,OAAO,EAAEC,EAAE,EAAkB,GAAKd,aAAa,GAAG,CAACc,OAAOrB,YAAYqB;IACnF;IAGA,KAAK,MAAMC,eAAe;QACtBC;QACAC;QACAC;QACAC;QACAC;QACAC;KACH,CACGjB,UAAU,gBAAgB,CAACW,aAAoB,CAAC;IAEpDX,UAAU,QAAQ,CAACkB;IAEnB,OAAO;QACH,WAAWlB,UAAU,OAAO,CAACmB;QAC7B3B;QACAC;QACAK;QACAJ;QACAK;QACAH;QACA,gBAAgB;YACZ,MAAMhB,aAAaE;YACnBa,iBAAiBf,WAAW,OAAO;YACnC,OAAOA;QACX;IACJ;AACJ;AAEAwC,SAAS,0CAA0C;IAC/CC,GAAG,6CAA6C;QAC5C,MAAM,EAAEC,SAAS,EAAE9B,KAAK,EAAEC,MAAM,EAAE,GAAGF;QACrC+B,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrC7B,OAAO,MAAM,CAAC,IAAI+B,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOjC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC;IACrD;IAEA6B,GAAG,sEAAsE;QACrE,MAAM,EAAEC,SAAS,EAAE7B,MAAM,EAAEK,aAAa,EAAE,GAAGP;QAC7C+B,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMzC,GAAG,OAAO,CAAC,IAAM4C,OAAO3B,eAAe,gBAAgB;QAC7DwB,UAAU,KAAK;QACf7B,OAAO,MAAM,CAAC,IAAI+B,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOH,UAAU,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;IACtC;IAEAD,GAAG,oDAAoD;QACnD,MAAM,EAAEC,SAAS,EAAE9B,KAAK,EAAEC,MAAM,EAAEK,aAAa,EAAE,GAAGP;QACpD+B,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMzC,GAAG,OAAO,CAAC,IAAM4C,OAAO3B,eAAe,gBAAgB;QAC7DwB,UAAU,QAAQ;QAClB7B,OAAO,MAAM,CAAC,IAAI+B,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOjC,OAAO,YAAY,CAAC;QAC3BiC,OAAOjC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;QAC/CiC,OAAOjC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;IACnD;IAEA6B,GAAG,8DAA8D;QAC7D,MAAM,EAAEC,SAAS,EAAE9B,KAAK,EAAEE,MAAM,EAAEK,aAAa,EAAE,GAAGR;QACpD,MAAM+B,UAAU,YAAY,CAAC;QAE7B,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMzC,GAAG,OAAO,CAAC,IAAM4C,OAAO1B,eAAe,gBAAgB;QAC7DL,OAAO,OAAO,CAACL,YAAY;QAE3B,MAAMoC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAO1B,eAAe,oBAAoB,CACtC0B,OAAO,gBAAgB,CAAC;YAAE,YAAY;YAAU,MAAM;gBAAE,QAAQ;oBAAE,OAAO;gBAAQ;YAAE;QAAE;QAEzFA,OAAOjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;YAAE,OAAO;QAAI;IACnE;IAEA6B,GAAG,mEAAmE;QAClE,MAAM,EAAEC,SAAS,EAAE5B,MAAM,EAAEK,aAAa,EAAE2B,cAAc,EAAE,GAAGnC;QAC7D,MAAMX,aAAa8C;QACnB,MAAMJ,UAAU,YAAY,CAAC;QAE7B,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMA,UAAU,YAAY,CAAC;QAC7B1C,WAAW,OAAO,CAAC;YAAE,OAAO;QAAsB;QAElDc,OAAO,OAAO,CAACL,YAAY;QAE3B,MAAMoC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAO1B,eAAe,GAAG,CAAC,gBAAgB;QAC1C0B,OAAOH,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC;IAC7C;IAEAD,GAAG,8EAA8E;QAC7E,MAAM,EAAEC,SAAS,EAAE9B,KAAK,EAAEE,MAAM,EAAEK,aAAa,EAAE,GAAGR;QACpD,MAAM+B,UAAU,YAAY,CAAC;QAE7B,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMzC,GAAG,OAAO,CAAC,IAAM4C,OAAO1B,eAAe,gBAAgB;QAC7D,MAAMuB,UAAU,YAAY,CAAC;QAC7B5B,OAAO,OAAO,CAACL,YAAY;QAG3B,MAAMoC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAO1B,eAAe,qBAAqB,CAAC;QAC5C0B,OAAO1B,eAAe,oBAAoB,CACtC0B,OAAO,gBAAgB,CAAC;YAAE,YAAY;QAAS;QAEnDA,OAAOH,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC;QACzCG,OAAOjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC;QAC/CiC,OAAOjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,CAAC;YAAE,OAAO;QAAI;IAC/D;IAEA6B,GAAG,+DAA+D;QAC9D,MAAM,EAAEC,SAAS,EAAE5B,MAAM,EAAEK,aAAa,EAAEH,YAAY,EAAE,GAAGL;QAC3D,MAAM+B,UAAU,YAAY,CAAC;QAE7B,MAAMC,SAASD,UAAU,YAAY;QACrC,MAAMzC,GAAG,OAAO,CAAC,IAAM4C,OAAO1B,eAAe,gBAAgB;QAE7D,MAAM4B,QAAQ7C;QACdc,aAAa,GAAG,CAAC,KAAK+B,MAAM,OAAO;QACnC,MAAMC,UAAUN,UAAU,YAAY,CAAC;QACvC5B,OAAO,OAAO,CAACL,YAAY;QAG3B,MAAMoC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOH,UAAU,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;QAElCK,MAAM,OAAO,CAACtC,YAAY;QAC1B,MAAMuC;QACNH,OAAOH,UAAU,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;IACtC;AACJ"}
|
|
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(\"creates a new entry in the folder it was started in\", async () => {\n const { presenter, create, createExecute } = setup();\n presenter.setFolderId(\"folder-1\");\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n create.resolve(createEntry(\"A\"));\n\n await expect(saving).resolves.toBe(true);\n expect(createExecute).toHaveBeenCalledWith(\n expect.objectContaining({\n data: { values: { title: \"Draft\" }, wbyAco_location: { folderId: \"folder-1\" } }\n })\n );\n });\n\n it(\"does not carry the folder over to an entry started after a reset\", async () => {\n const { presenter, create, createExecute } = setup();\n presenter.setFolderId(\"folder-1\");\n presenter.reset();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n create.resolve(createEntry(\"A\"));\n\n await expect(saving).resolves.toBe(true);\n expect(createExecute).toHaveBeenCalledWith(\n expect.objectContaining({ data: { values: { title: \"Draft\" } } })\n );\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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"}
|
|
@@ -28,6 +28,7 @@ const ContentEntryFormView = observer(()=>{
|
|
|
28
28
|
const showWizard = hasWizard && null === vm.form;
|
|
29
29
|
useEffect(()=>{
|
|
30
30
|
if ("new" === entryId) {
|
|
31
|
+
formPresenter.setFolderId(listPresenter.folders.vm.currentFolderId);
|
|
31
32
|
if (!newEntryWizard) formPresenter.newEntry();
|
|
32
33
|
} else if (entryId) {
|
|
33
34
|
formPresenter.loadRevision(entryId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/views/ContentEntryFormView.js","sources":["../../../../src/presentation/contentEntries/views/ContentEntryFormView.tsx"],"sourcesContent":["import React, { useEffect } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport { observer } from \"mobx-react-lite\";\nimport { useFeature, useRouter } from \"@webiny/app\";\nimport { Buttons, useDialogs, useRoute } from \"@webiny/app-admin\";\nimport { HeaderBar, Heading, Icon, IconButton, OverlayLoader, Tooltip } from \"@webiny/admin-ui\";\nimport { ReactComponent as BackIcon } from \"@webiny/icons/arrow_back.svg\";\nimport { ReactComponent as InfoIcon } from \"@webiny/icons/info.svg\";\nimport { useContentEntryEditorConfig } from \"~/admin/config/contentEntries/index.js\";\nimport { Routes } from \"~/routes.js\";\nimport {\n Container,\n ScrollArea,\n ContentEntryFormContent,\n ContentEntryForm\n} from \"./layout/index.js\";\nimport { RevisionsListFeature } from \"../revisionsList/feature.js\";\nimport { useContentEntriesPresenter } from \"~/presentation/contentEntries/list/useContentEntriesPresenter.js\";\nimport { useContentEntryFormPresenter } from \"~/presentation/contentEntries/form/useContentEntryFormPresenter.js\";\nimport { RevisionDrawer } from \"./RevisionDrawer.js\";\nimport { OlderRevisionAlert } from \"./OlderRevisionAlert.js\";\n\nexport const ContentEntryFormView = observer(() => {\n const listPresenter = useContentEntriesPresenter();\n const formPresenter = useContentEntryFormPresenter();\n const { presenter: revisionsPresenter } = useFeature(RevisionsListFeature);\n const { newEntryWizard, width } = useContentEntryEditorConfig();\n const router = useRouter();\n const dialogs = useDialogs();\n const { route } = useRoute(Routes.ContentEntries.List);\n\n const entryId = listPresenter.vm.selectedEntryId;\n const { vm } = formPresenter;\n\n const hasWizard = entryId === \"new\" && newEntryWizard !== null;\n const showWizard = hasWizard && vm.form === null;\n\n useEffect(() => {\n if (entryId === \"new\") {\n if (!newEntryWizard) {\n formPresenter.newEntry();\n }\n } else if (entryId) {\n formPresenter.loadRevision(entryId);\n revisionsPresenter.init(entryId);\n }\n\n return () => {\n formPresenter.reset();\n revisionsPresenter.dispose();\n };\n }, [entryId]);\n\n useEffect(() => {\n return router.addTransitionGuard({\n guard: () => formPresenter.vm.isDirty,\n onBlocked: () => {\n dialogs.showDialog({\n title: \"Confirm Navigation\",\n content:\n \"There are some unsaved changes! Are you sure you want to navigate away and discard all changes?\",\n acceptLabel: \"Yes!\",\n cancelLabel: \"No, stay here.\",\n onAccept: () => {\n // We must reset the form to prevent the guard from kicking in again.\n formPresenter.reset();\n router.confirmTransition();\n },\n onClose: () => router.cancelTransition()\n });\n }\n });\n }, []);\n\n const handleBack = () => {\n const { modelId, folderId } = route.params;\n router.goToRoute(Routes.ContentEntries.List, { modelId, folderId });\n };\n\n return (\n <Container>\n <Helmet title={vm.entry?.meta?.title || listPresenter.vm.model.name} />\n <HeaderBar\n start={\n <EntryFormHeaderLeft\n onBack={handleBack}\n title={vm.entry?.meta?.title || `New ${listPresenter.vm.model.name}`}\n isNewEntry={vm.isNewEntry}\n modelName={listPresenter.vm.model.name}\n status={vm.entry?.meta?.status ?? null}\n />\n }\n end={<EntryFormHeaderRight />}\n />\n <ScrollArea>\n {vm.loading ? <OverlayLoader text={vm.loading} /> : null}\n <OlderRevisionAlert />\n {showWizard ? (\n newEntryWizard\n ) : (\n <ContentEntryFormContent width={width}>\n <div className={\"bg-neutral-base rounded-lg p-lg\"}>\n <ContentEntryForm />\n </div>\n </ContentEntryFormContent>\n )}\n </ScrollArea>\n <RevisionDrawer />\n </Container>\n );\n});\n\ninterface EntryFormHeaderLeftProps {\n onBack: () => void;\n title: string;\n isNewEntry: boolean;\n modelName: string;\n status: string | null;\n}\n\nconst EntryFormHeaderLeft = ({\n onBack,\n title,\n isNewEntry,\n modelName,\n status\n}: EntryFormHeaderLeftProps) => {\n return (\n <div className={\"flex items-center gap-sm\"}>\n <IconButton variant={\"ghost\"} onClick={onBack} icon={<BackIcon />} />\n <Heading level={5} className={`text-neutral-primary${isNewEntry ? \" opacity-50\" : \"\"}`}>\n {title}\n </Heading>\n <Tooltip\n content={`Model: ${modelName}${status ? ` - Status: ${status}` : \"\"}`}\n trigger={\n <Icon icon={<InfoIcon />} label={\"Info\"} size={\"sm\"} color={\"neutral-light\"} />\n }\n />\n </div>\n );\n};\n\nconst EntryFormHeaderRight = observer(() => {\n const presenter = useContentEntryFormPresenter();\n const { buttonActions } = useContentEntryEditorConfig();\n\n const { canSave, canPublish, canUnpublish, canDelete } = presenter.vm;\n\n if (!canSave && !canPublish && !canUnpublish && !canDelete) {\n return <div />;\n }\n\n return (\n <div className={\"flex items-center gap-sm\"}>\n <Buttons actions={buttonActions} />\n </div>\n );\n});\n"],"names":["ContentEntryFormView","observer","listPresenter","useContentEntriesPresenter","formPresenter","useContentEntryFormPresenter","revisionsPresenter","useFeature","RevisionsListFeature","newEntryWizard","width","useContentEntryEditorConfig","router","useRouter","dialogs","useDialogs","route","useRoute","Routes","entryId","vm","hasWizard","showWizard","useEffect","handleBack","modelId","folderId","Container","Helmet","HeaderBar","EntryFormHeaderLeft","EntryFormHeaderRight","ScrollArea","OverlayLoader","OlderRevisionAlert","ContentEntryFormContent","ContentEntryForm","RevisionDrawer","onBack","title","isNewEntry","modelName","status","IconButton","BackIcon","Heading","Tooltip","Icon","InfoIcon","presenter","buttonActions","canSave","canPublish","canUnpublish","canDelete","Buttons"],"mappings":";;;;;;;;;;;;;;;;AAsBO,MAAMA,uBAAuBC,SAAS;IACzC,MAAMC,gBAAgBC;IACtB,MAAMC,gBAAgBC;IACtB,MAAM,EAAE,WAAWC,kBAAkB,EAAE,GAAGC,WAAWC;IACrD,MAAM,EAAEC,cAAc,EAAEC,KAAK,EAAE,GAAGC;IAClC,MAAMC,SAASC;IACf,MAAMC,UAAUC;IAChB,MAAM,EAAEC,KAAK,EAAE,GAAGC,SAASC,OAAO,cAAc,CAAC,IAAI;IAErD,MAAMC,UAAUjB,cAAc,EAAE,CAAC,eAAe;IAChD,MAAM,EAAEkB,EAAE,EAAE,GAAGhB;IAEf,MAAMiB,YAAYF,AAAY,UAAZA,WAAqBV,AAAmB,SAAnBA;IACvC,MAAMa,aAAaD,aAAaD,AAAY,SAAZA,GAAG,IAAI;IAEvCG,UAAU;QACN,IAAIJ,AAAY,UAAZA,
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/views/ContentEntryFormView.js","sources":["../../../../src/presentation/contentEntries/views/ContentEntryFormView.tsx"],"sourcesContent":["import React, { useEffect } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport { observer } from \"mobx-react-lite\";\nimport { useFeature, useRouter } from \"@webiny/app\";\nimport { Buttons, useDialogs, useRoute } from \"@webiny/app-admin\";\nimport { HeaderBar, Heading, Icon, IconButton, OverlayLoader, Tooltip } from \"@webiny/admin-ui\";\nimport { ReactComponent as BackIcon } from \"@webiny/icons/arrow_back.svg\";\nimport { ReactComponent as InfoIcon } from \"@webiny/icons/info.svg\";\nimport { useContentEntryEditorConfig } from \"~/admin/config/contentEntries/index.js\";\nimport { Routes } from \"~/routes.js\";\nimport {\n Container,\n ScrollArea,\n ContentEntryFormContent,\n ContentEntryForm\n} from \"./layout/index.js\";\nimport { RevisionsListFeature } from \"../revisionsList/feature.js\";\nimport { useContentEntriesPresenter } from \"~/presentation/contentEntries/list/useContentEntriesPresenter.js\";\nimport { useContentEntryFormPresenter } from \"~/presentation/contentEntries/form/useContentEntryFormPresenter.js\";\nimport { RevisionDrawer } from \"./RevisionDrawer.js\";\nimport { OlderRevisionAlert } from \"./OlderRevisionAlert.js\";\n\nexport const ContentEntryFormView = observer(() => {\n const listPresenter = useContentEntriesPresenter();\n const formPresenter = useContentEntryFormPresenter();\n const { presenter: revisionsPresenter } = useFeature(RevisionsListFeature);\n const { newEntryWizard, width } = useContentEntryEditorConfig();\n const router = useRouter();\n const dialogs = useDialogs();\n const { route } = useRoute(Routes.ContentEntries.List);\n\n const entryId = listPresenter.vm.selectedEntryId;\n const { vm } = formPresenter;\n\n const hasWizard = entryId === \"new\" && newEntryWizard !== null;\n const showWizard = hasWizard && vm.form === null;\n\n useEffect(() => {\n if (entryId === \"new\") {\n // Wizards start the entry later, so the folder is set for them too.\n formPresenter.setFolderId(listPresenter.folders.vm.currentFolderId);\n if (!newEntryWizard) {\n formPresenter.newEntry();\n }\n } else if (entryId) {\n formPresenter.loadRevision(entryId);\n revisionsPresenter.init(entryId);\n }\n\n return () => {\n formPresenter.reset();\n revisionsPresenter.dispose();\n };\n }, [entryId]);\n\n useEffect(() => {\n return router.addTransitionGuard({\n guard: () => formPresenter.vm.isDirty,\n onBlocked: () => {\n dialogs.showDialog({\n title: \"Confirm Navigation\",\n content:\n \"There are some unsaved changes! Are you sure you want to navigate away and discard all changes?\",\n acceptLabel: \"Yes!\",\n cancelLabel: \"No, stay here.\",\n onAccept: () => {\n // We must reset the form to prevent the guard from kicking in again.\n formPresenter.reset();\n router.confirmTransition();\n },\n onClose: () => router.cancelTransition()\n });\n }\n });\n }, []);\n\n const handleBack = () => {\n const { modelId, folderId } = route.params;\n router.goToRoute(Routes.ContentEntries.List, { modelId, folderId });\n };\n\n return (\n <Container>\n <Helmet title={vm.entry?.meta?.title || listPresenter.vm.model.name} />\n <HeaderBar\n start={\n <EntryFormHeaderLeft\n onBack={handleBack}\n title={vm.entry?.meta?.title || `New ${listPresenter.vm.model.name}`}\n isNewEntry={vm.isNewEntry}\n modelName={listPresenter.vm.model.name}\n status={vm.entry?.meta?.status ?? null}\n />\n }\n end={<EntryFormHeaderRight />}\n />\n <ScrollArea>\n {vm.loading ? <OverlayLoader text={vm.loading} /> : null}\n <OlderRevisionAlert />\n {showWizard ? (\n newEntryWizard\n ) : (\n <ContentEntryFormContent width={width}>\n <div className={\"bg-neutral-base rounded-lg p-lg\"}>\n <ContentEntryForm />\n </div>\n </ContentEntryFormContent>\n )}\n </ScrollArea>\n <RevisionDrawer />\n </Container>\n );\n});\n\ninterface EntryFormHeaderLeftProps {\n onBack: () => void;\n title: string;\n isNewEntry: boolean;\n modelName: string;\n status: string | null;\n}\n\nconst EntryFormHeaderLeft = ({\n onBack,\n title,\n isNewEntry,\n modelName,\n status\n}: EntryFormHeaderLeftProps) => {\n return (\n <div className={\"flex items-center gap-sm\"}>\n <IconButton variant={\"ghost\"} onClick={onBack} icon={<BackIcon />} />\n <Heading level={5} className={`text-neutral-primary${isNewEntry ? \" opacity-50\" : \"\"}`}>\n {title}\n </Heading>\n <Tooltip\n content={`Model: ${modelName}${status ? ` - Status: ${status}` : \"\"}`}\n trigger={\n <Icon icon={<InfoIcon />} label={\"Info\"} size={\"sm\"} color={\"neutral-light\"} />\n }\n />\n </div>\n );\n};\n\nconst EntryFormHeaderRight = observer(() => {\n const presenter = useContentEntryFormPresenter();\n const { buttonActions } = useContentEntryEditorConfig();\n\n const { canSave, canPublish, canUnpublish, canDelete } = presenter.vm;\n\n if (!canSave && !canPublish && !canUnpublish && !canDelete) {\n return <div />;\n }\n\n return (\n <div className={\"flex items-center gap-sm\"}>\n <Buttons actions={buttonActions} />\n </div>\n );\n});\n"],"names":["ContentEntryFormView","observer","listPresenter","useContentEntriesPresenter","formPresenter","useContentEntryFormPresenter","revisionsPresenter","useFeature","RevisionsListFeature","newEntryWizard","width","useContentEntryEditorConfig","router","useRouter","dialogs","useDialogs","route","useRoute","Routes","entryId","vm","hasWizard","showWizard","useEffect","handleBack","modelId","folderId","Container","Helmet","HeaderBar","EntryFormHeaderLeft","EntryFormHeaderRight","ScrollArea","OverlayLoader","OlderRevisionAlert","ContentEntryFormContent","ContentEntryForm","RevisionDrawer","onBack","title","isNewEntry","modelName","status","IconButton","BackIcon","Heading","Tooltip","Icon","InfoIcon","presenter","buttonActions","canSave","canPublish","canUnpublish","canDelete","Buttons"],"mappings":"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"}
|
|
@@ -39,6 +39,7 @@ const RefInputInner = observer(({ field })=>{
|
|
|
39
39
|
value: vm.value,
|
|
40
40
|
options: vm.options,
|
|
41
41
|
validation: field.validation,
|
|
42
|
+
shouldFilter: false,
|
|
42
43
|
onValueSearch: (query)=>presenter.search(query),
|
|
43
44
|
onValueChange: (selectedEntryId)=>{
|
|
44
45
|
if (!selectedEntryId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/fieldRenderers/ref/CmsRefInputRenderer.js","sources":["../../../../src/presentation/fieldRenderers/ref/CmsRefInputRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport type { IFieldVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { AutoComplete } from \"@webiny/admin-ui\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\nimport type { RefFieldRendererSettings } from \"./types.js\";\nimport { RefSingleAutocompletePresenterFeature } from \"./autocomplete/single/feature.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n refInput: { fieldType: \"ref\"; settings: RefFieldRendererSettings };\n }\n}\n\nexport const CmsRefInputRenderer = createFieldRenderer<\"refInput\">(({ field }) => {\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefSingleAutocompletePresenterFeature.register(child);\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n <RefInputInner field={field} />\n </DiContainerProvider>\n );\n});\n\ninterface InnerFieldProps {\n field: IFieldVM & { rendererSettings: RefFieldRendererSettings };\n}\n\nconst RefInputInner = observer(({ field }: InnerFieldProps) => {\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n const settings = field.rendererSettings;\n const modelIds = (settings.models || []).map(m => m.modelId);\n const value = field.value as CmsReferenceValue | null;\n\n useEffect(() => {\n presenter.init({ modelIds, value });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n disabled={field.disabled}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n validation={field.validation}\n onValueSearch={query => presenter.search(query)}\n onValueChange={selectedEntryId => {\n if (!selectedEntryId) {\n presenter.clear();\n field.onChange(null);\n return;\n }\n const ref = presenter.select(selectedEntryId);\n if (ref) {\n field.onChange(ref);\n }\n }}\n onValueReset={() => {\n presenter.clear();\n field.onChange(null);\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n"],"names":["CmsRefInputRenderer","createFieldRenderer","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefSingleAutocompletePresenterFeature","DiContainerProvider","RefInputInner","observer","presenter","useFeature","settings","modelIds","m","value","useEffect","vm","AutoComplete","query","selectedEntryId","ref"],"mappings":";;;;;;AAgBO,MAAMA,sBAAsBC,oBAAgC,CAAC,EAAEC,KAAK,EAAE;IACzE,MAAMC,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,sCAAsC,QAAQ,CAACD;QAC/C,OAAOA;IACX,GAAG,EAAE;IAEL,OAAO,WAAP,GACI,oBAACE,qBAAmBA;QAAC,WAAWJ;qBAC5B,oBAACK,eAAaA;QAAC,OAAOR;;AAGlC;AAMA,MAAMQ,gBAAgBC,SAAS,CAAC,EAAET,KAAK,EAAmB;IACtD,MAAM,EAAEU,SAAS,EAAE,GAAGC,WAAWL;IAEjC,MAAMM,WAAWZ,MAAM,gBAAgB;IACvC,MAAMa,WAAYD,AAAAA,CAAAA,SAAS,MAAM,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAKA,EAAE,OAAO;IAC3D,MAAMC,QAAQf,MAAM,KAAK;IAEzBgB,UAAU;QACNN,UAAU,IAAI,CAAC;YAAEG;YAAUE;QAAM;IACrC,GAAG,EAAE;IAEL,MAAME,KAAKP,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACQ,cAAYA;QACT,OAAOlB,MAAM,KAAK;QAClB,aAAaA,MAAM,WAAW;QAC9B,MAAMA,MAAM,IAAI;QAChB,MAAMA,MAAM,IAAI;QAChB,UAAUA,MAAM,QAAQ;QACxB,SAASiB,GAAG,OAAO;QACnB,OAAOA,GAAG,KAAK;QACf,SAASA,GAAG,OAAO;QACnB,YAAYjB,MAAM,UAAU;QAC5B,eAAemB,CAAAA,QAAST,UAAU,MAAM,CAACS;QACzC,eAAeC,CAAAA;YACX,IAAI,CAACA,iBAAiB;gBAClBV,UAAU,KAAK;gBACfV,MAAM,QAAQ,CAAC;gBACf;YACJ;YACA,MAAMqB,MAAMX,UAAU,MAAM,CAACU;YAC7B,IAAIC,KACArB,MAAM,QAAQ,CAACqB;QAEvB;QACA,cAAc;YACVX,UAAU,KAAK;YACfV,MAAM,QAAQ,CAAC;QACnB;QACA,oBAAoBiB,GAAG,QAAQ;;AAG3C"}
|
|
1
|
+
{"version":3,"file":"presentation/fieldRenderers/ref/CmsRefInputRenderer.js","sources":["../../../../src/presentation/fieldRenderers/ref/CmsRefInputRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport type { IFieldVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { AutoComplete } from \"@webiny/admin-ui\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\nimport type { RefFieldRendererSettings } from \"./types.js\";\nimport { RefSingleAutocompletePresenterFeature } from \"./autocomplete/single/feature.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n refInput: { fieldType: \"ref\"; settings: RefFieldRendererSettings };\n }\n}\n\nexport const CmsRefInputRenderer = createFieldRenderer<\"refInput\">(({ field }) => {\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefSingleAutocompletePresenterFeature.register(child);\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n <RefInputInner field={field} />\n </DiContainerProvider>\n );\n});\n\ninterface InnerFieldProps {\n field: IFieldVM & { rendererSettings: RefFieldRendererSettings };\n}\n\nconst RefInputInner = observer(({ field }: InnerFieldProps) => {\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n const settings = field.rendererSettings;\n const modelIds = (settings.models || []).map(m => m.modelId);\n const value = field.value as CmsReferenceValue | null;\n\n useEffect(() => {\n presenter.init({ modelIds, value });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n disabled={field.disabled}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n validation={field.validation}\n shouldFilter={false}\n onValueSearch={query => presenter.search(query)}\n onValueChange={selectedEntryId => {\n if (!selectedEntryId) {\n presenter.clear();\n field.onChange(null);\n return;\n }\n const ref = presenter.select(selectedEntryId);\n if (ref) {\n field.onChange(ref);\n }\n }}\n onValueReset={() => {\n presenter.clear();\n field.onChange(null);\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n"],"names":["CmsRefInputRenderer","createFieldRenderer","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefSingleAutocompletePresenterFeature","DiContainerProvider","RefInputInner","observer","presenter","useFeature","settings","modelIds","m","value","useEffect","vm","AutoComplete","query","selectedEntryId","ref"],"mappings":";;;;;;AAgBO,MAAMA,sBAAsBC,oBAAgC,CAAC,EAAEC,KAAK,EAAE;IACzE,MAAMC,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,sCAAsC,QAAQ,CAACD;QAC/C,OAAOA;IACX,GAAG,EAAE;IAEL,OAAO,WAAP,GACI,oBAACE,qBAAmBA;QAAC,WAAWJ;qBAC5B,oBAACK,eAAaA;QAAC,OAAOR;;AAGlC;AAMA,MAAMQ,gBAAgBC,SAAS,CAAC,EAAET,KAAK,EAAmB;IACtD,MAAM,EAAEU,SAAS,EAAE,GAAGC,WAAWL;IAEjC,MAAMM,WAAWZ,MAAM,gBAAgB;IACvC,MAAMa,WAAYD,AAAAA,CAAAA,SAAS,MAAM,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAKA,EAAE,OAAO;IAC3D,MAAMC,QAAQf,MAAM,KAAK;IAEzBgB,UAAU;QACNN,UAAU,IAAI,CAAC;YAAEG;YAAUE;QAAM;IACrC,GAAG,EAAE;IAEL,MAAME,KAAKP,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACQ,cAAYA;QACT,OAAOlB,MAAM,KAAK;QAClB,aAAaA,MAAM,WAAW;QAC9B,MAAMA,MAAM,IAAI;QAChB,MAAMA,MAAM,IAAI;QAChB,UAAUA,MAAM,QAAQ;QACxB,SAASiB,GAAG,OAAO;QACnB,OAAOA,GAAG,KAAK;QACf,SAASA,GAAG,OAAO;QACnB,YAAYjB,MAAM,UAAU;QAC5B,cAAc;QACd,eAAemB,CAAAA,QAAST,UAAU,MAAM,CAACS;QACzC,eAAeC,CAAAA;YACX,IAAI,CAACA,iBAAiB;gBAClBV,UAAU,KAAK;gBACfV,MAAM,QAAQ,CAAC;gBACf;YACJ;YACA,MAAMqB,MAAMX,UAAU,MAAM,CAACU;YAC7B,IAAIC,KACArB,MAAM,QAAQ,CAACqB;QAEvB;QACA,cAAc;YACVX,UAAU,KAAK;YACfV,MAAM,QAAQ,CAAC;QACnB;QACA,oBAAoBiB,GAAG,QAAQ;;AAG3C"}
|
|
@@ -40,6 +40,7 @@ const RefInputsInner = observer(({ field })=>{
|
|
|
40
40
|
options: vm.options,
|
|
41
41
|
uniqueValues: true,
|
|
42
42
|
validation: field.validation,
|
|
43
|
+
shouldFilter: false,
|
|
43
44
|
onValueSearch: (query)=>presenter.search(query),
|
|
44
45
|
onValuesChange: (entryIds)=>{
|
|
45
46
|
const refs = presenter.select(entryIds);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/fieldRenderers/ref/CmsRefInputsRenderer.js","sources":["../../../../src/presentation/fieldRenderers/ref/CmsRefInputsRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport type { IFieldVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { MultiAutoComplete } from \"@webiny/admin-ui\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\nimport type { RefFieldRendererSettings } from \"./types.js\";\nimport { RefMultiAutocompletePresenterFeature } from \"./autocomplete/multi/feature.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n refInputs: { fieldType: \"ref\"; settings: RefFieldRendererSettings };\n }\n}\n\nexport const CmsRefInputsRenderer = createFieldRenderer<\"refInputs\">(({ field }) => {\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefMultiAutocompletePresenterFeature.register(child);\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n <RefInputsInner field={field} />\n </DiContainerProvider>\n );\n});\n\ninterface InnerFieldProps {\n field: IFieldVM & { rendererSettings: RefFieldRendererSettings };\n}\n\nconst RefInputsInner = observer(({ field }: InnerFieldProps) => {\n const { presenter } = useFeature(RefMultiAutocompletePresenterFeature);\n\n const settings = field.rendererSettings;\n const modelIds = (settings.models || []).map(m => m.modelId);\n const values: CmsReferenceValue[] = Array.isArray(field.value) ? field.value : [];\n\n useEffect(() => {\n presenter.init({ modelIds, values });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <MultiAutoComplete\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n disabled={field.disabled}\n loading={vm.loading}\n values={vm.values}\n options={vm.options}\n uniqueValues\n validation={field.validation}\n onValueSearch={query => presenter.search(query)}\n onValuesChange={entryIds => {\n const refs = presenter.select(entryIds);\n field.onChange(refs.length > 0 ? refs : null);\n }}\n onValuesReset={() => {\n presenter.clear();\n field.onChange(null);\n }}\n />\n );\n});\n"],"names":["CmsRefInputsRenderer","createFieldRenderer","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefMultiAutocompletePresenterFeature","DiContainerProvider","RefInputsInner","observer","presenter","useFeature","settings","modelIds","m","values","Array","useEffect","vm","MultiAutoComplete","query","entryIds","refs"],"mappings":";;;;;;AAgBO,MAAMA,uBAAuBC,oBAAiC,CAAC,EAAEC,KAAK,EAAE;IAC3E,MAAMC,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,qCAAqC,QAAQ,CAACD;QAC9C,OAAOA;IACX,GAAG,EAAE;IAEL,OAAO,WAAP,GACI,oBAACE,qBAAmBA;QAAC,WAAWJ;qBAC5B,oBAACK,gBAAcA;QAAC,OAAOR;;AAGnC;AAMA,MAAMQ,iBAAiBC,SAAS,CAAC,EAAET,KAAK,EAAmB;IACvD,MAAM,EAAEU,SAAS,EAAE,GAAGC,WAAWL;IAEjC,MAAMM,WAAWZ,MAAM,gBAAgB;IACvC,MAAMa,WAAYD,AAAAA,CAAAA,SAAS,MAAM,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAKA,EAAE,OAAO;IAC3D,MAAMC,SAA8BC,MAAM,OAAO,CAAChB,MAAM,KAAK,IAAIA,MAAM,KAAK,GAAG,EAAE;IAEjFiB,UAAU;QACNP,UAAU,IAAI,CAAC;YAAEG;YAAUE;QAAO;IACtC,GAAG,EAAE;IAEL,MAAMG,KAAKR,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACS,mBAAiBA;QACd,OAAOnB,MAAM,KAAK;QAClB,aAAaA,MAAM,WAAW;QAC9B,MAAMA,MAAM,IAAI;QAChB,MAAMA,MAAM,IAAI;QAChB,UAAUA,MAAM,QAAQ;QACxB,SAASkB,GAAG,OAAO;QACnB,QAAQA,GAAG,MAAM;QACjB,SAASA,GAAG,OAAO;QACnB;QACA,YAAYlB,MAAM,UAAU;QAC5B,eAAeoB,CAAAA,QAASV,UAAU,MAAM,CAACU;QACzC,gBAAgBC,CAAAA;YACZ,MAAMC,OAAOZ,UAAU,MAAM,CAACW;YAC9BrB,MAAM,QAAQ,CAACsB,KAAK,MAAM,GAAG,IAAIA,OAAO;QAC5C;QACA,eAAe;YACXZ,UAAU,KAAK;YACfV,MAAM,QAAQ,CAAC;QACnB;;AAGZ"}
|
|
1
|
+
{"version":3,"file":"presentation/fieldRenderers/ref/CmsRefInputsRenderer.js","sources":["../../../../src/presentation/fieldRenderers/ref/CmsRefInputsRenderer.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport type { IFieldVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { MultiAutoComplete } from \"@webiny/admin-ui\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\nimport type { RefFieldRendererSettings } from \"./types.js\";\nimport { RefMultiAutocompletePresenterFeature } from \"./autocomplete/multi/feature.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n refInputs: { fieldType: \"ref\"; settings: RefFieldRendererSettings };\n }\n}\n\nexport const CmsRefInputsRenderer = createFieldRenderer<\"refInputs\">(({ field }) => {\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n RefMultiAutocompletePresenterFeature.register(child);\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n <RefInputsInner field={field} />\n </DiContainerProvider>\n );\n});\n\ninterface InnerFieldProps {\n field: IFieldVM & { rendererSettings: RefFieldRendererSettings };\n}\n\nconst RefInputsInner = observer(({ field }: InnerFieldProps) => {\n const { presenter } = useFeature(RefMultiAutocompletePresenterFeature);\n\n const settings = field.rendererSettings;\n const modelIds = (settings.models || []).map(m => m.modelId);\n const values: CmsReferenceValue[] = Array.isArray(field.value) ? field.value : [];\n\n useEffect(() => {\n presenter.init({ modelIds, values });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <MultiAutoComplete\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n disabled={field.disabled}\n loading={vm.loading}\n values={vm.values}\n options={vm.options}\n uniqueValues\n validation={field.validation}\n shouldFilter={false}\n onValueSearch={query => presenter.search(query)}\n onValuesChange={entryIds => {\n const refs = presenter.select(entryIds);\n field.onChange(refs.length > 0 ? refs : null);\n }}\n onValuesReset={() => {\n presenter.clear();\n field.onChange(null);\n }}\n />\n );\n});\n"],"names":["CmsRefInputsRenderer","createFieldRenderer","field","parentContainer","useContainer","scopedContainer","useMemo","child","RefMultiAutocompletePresenterFeature","DiContainerProvider","RefInputsInner","observer","presenter","useFeature","settings","modelIds","m","values","Array","useEffect","vm","MultiAutoComplete","query","entryIds","refs"],"mappings":";;;;;;AAgBO,MAAMA,uBAAuBC,oBAAiC,CAAC,EAAEC,KAAK,EAAE;IAC3E,MAAMC,kBAAkBC;IAExB,MAAMC,kBAAkBC,QAAQ;QAC5B,MAAMC,QAAQJ,gBAAgB,oBAAoB;QAClDK,qCAAqC,QAAQ,CAACD;QAC9C,OAAOA;IACX,GAAG,EAAE;IAEL,OAAO,WAAP,GACI,oBAACE,qBAAmBA;QAAC,WAAWJ;qBAC5B,oBAACK,gBAAcA;QAAC,OAAOR;;AAGnC;AAMA,MAAMQ,iBAAiBC,SAAS,CAAC,EAAET,KAAK,EAAmB;IACvD,MAAM,EAAEU,SAAS,EAAE,GAAGC,WAAWL;IAEjC,MAAMM,WAAWZ,MAAM,gBAAgB;IACvC,MAAMa,WAAYD,AAAAA,CAAAA,SAAS,MAAM,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAKA,EAAE,OAAO;IAC3D,MAAMC,SAA8BC,MAAM,OAAO,CAAChB,MAAM,KAAK,IAAIA,MAAM,KAAK,GAAG,EAAE;IAEjFiB,UAAU;QACNP,UAAU,IAAI,CAAC;YAAEG;YAAUE;QAAO;IACtC,GAAG,EAAE;IAEL,MAAMG,KAAKR,UAAU,EAAE;IAEvB,OAAO,WAAP,GACI,oBAACS,mBAAiBA;QACd,OAAOnB,MAAM,KAAK;QAClB,aAAaA,MAAM,WAAW;QAC9B,MAAMA,MAAM,IAAI;QAChB,MAAMA,MAAM,IAAI;QAChB,UAAUA,MAAM,QAAQ;QACxB,SAASkB,GAAG,OAAO;QACnB,QAAQA,GAAG,MAAM;QACjB,SAASA,GAAG,OAAO;QACnB;QACA,YAAYlB,MAAM,UAAU;QAC5B,cAAc;QACd,eAAeoB,CAAAA,QAASV,UAAU,MAAM,CAACU;QACzC,gBAAgBC,CAAAA;YACZ,MAAMC,OAAOZ,UAAU,MAAM,CAACW;YAC9BrB,MAAM,QAAQ,CAACsB,KAAK,MAAM,GAAG,IAAIA,OAAO;QAC5C;QACA,eAAe;YACXZ,UAAU,KAAK;YACfV,MAAM,QAAQ,CAAC;QACnB;;AAGZ"}
|