@webiny/app-website-builder 6.5.0-beta.9 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.js +6 -66
- package/BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.js.map +1 -1
- package/BaseEditor/defaultConfig/Content/usePreviewDomain.d.ts +1 -6
- package/BaseEditor/defaultConfig/Content/usePreviewDomain.js +1 -26
- package/inputRenderers/ContentEntryInput.js +2 -0
- package/inputRenderers/ContentEntryInput.js.map +1 -1
- package/package.json +26 -26
- package/presentation/pages/PageEditor/Revisions/RevisionListItem.d.ts +3 -1
- package/presentation/pages/PageEditor/Revisions/RevisionListItem.js +3 -2
- package/presentation/pages/PageEditor/Revisions/RevisionListItem.js.map +1 -1
- package/presentation/pages/PageEditor/Revisions/RevisionsList.js +21 -2
- package/presentation/pages/PageEditor/Revisions/RevisionsList.js.map +1 -1
- package/presentation/pages/PageEditor/Revisions/useRevision.d.ts +5 -0
- package/presentation/pages/PageEditor/Revisions/useRevision.js +4 -2
- package/presentation/pages/PageEditor/Revisions/useRevision.js.map +1 -1
- package/presentation/pages/RevisionList/RevisionListPresenter.js +1 -1
- package/presentation/pages/RevisionList/RevisionListPresenter.js.map +1 -1
- package/presentation/pages/RevisionList/RevisionListPresenter.test.js +16 -0
- package/presentation/pages/RevisionList/RevisionListPresenter.test.js.map +1 -1
- package/BaseEditor/defaultConfig/Content/usePreviewDomain.js.map +0 -1
|
@@ -1,80 +1,20 @@
|
|
|
1
|
-
import react, {
|
|
2
|
-
import {
|
|
3
|
-
import { Button, DropdownMenu, IconButton, Input, Separator, cn } from "@webiny/admin-ui";
|
|
1
|
+
import react, { useEffect } from "react";
|
|
2
|
+
import { PreviewDomainMenu } from "@webiny/frontend-settings/exports/admin.js";
|
|
4
3
|
import { usePreviewDomain } from "../usePreviewDomain.js";
|
|
5
|
-
import { Bind, Form } from "@webiny/form";
|
|
6
|
-
import { validation } from "@webiny/validation";
|
|
7
4
|
import { useDocumentEditor } from "../../../../DocumentEditor/index.js";
|
|
8
5
|
import { Commands } from "../../../index.js";
|
|
9
6
|
const PreviewDomain = ()=>{
|
|
10
7
|
const editor = useDocumentEditor();
|
|
11
|
-
const
|
|
12
|
-
const { previewDomain, isOverridden, unsetPreviewDomain, setPreviewDomain } = usePreviewDomain();
|
|
13
|
-
const commitValue = useCallback((data)=>{
|
|
14
|
-
const value = data.previewDomain;
|
|
15
|
-
if (value.length > 0) setPreviewDomain(value);
|
|
16
|
-
else unsetPreviewDomain();
|
|
17
|
-
setIsOpen(false);
|
|
18
|
-
}, []);
|
|
19
|
-
const resetDomain = useCallback(()=>{
|
|
20
|
-
unsetPreviewDomain();
|
|
21
|
-
setIsOpen(false);
|
|
22
|
-
}, []);
|
|
8
|
+
const { previewDomain } = usePreviewDomain();
|
|
23
9
|
useEffect(()=>{
|
|
24
10
|
if (!previewDomain) return;
|
|
25
11
|
editor.executeCommand(Commands.RefreshPreview);
|
|
26
12
|
}, [
|
|
27
13
|
previewDomain
|
|
28
14
|
]);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
align: "center",
|
|
33
|
-
side: "bottom",
|
|
34
|
-
className: "shadow-lg",
|
|
35
|
-
onOpenChange: setIsOpen,
|
|
36
|
-
trigger: /*#__PURE__*/ react.createElement(IconButton, {
|
|
37
|
-
icon: /*#__PURE__*/ react.createElement(ReactComponent, null),
|
|
38
|
-
size: "md",
|
|
39
|
-
onClick: ()=>{},
|
|
40
|
-
variant: "ghost",
|
|
41
|
-
className: classNames
|
|
42
|
-
})
|
|
43
|
-
}, /*#__PURE__*/ react.createElement("div", {
|
|
44
|
-
className: "p-sm text-sm",
|
|
45
|
-
style: {
|
|
46
|
-
width: 300
|
|
47
|
-
}
|
|
48
|
-
}, /*#__PURE__*/ react.createElement(Form, {
|
|
49
|
-
data: {
|
|
50
|
-
previewDomain
|
|
51
|
-
},
|
|
52
|
-
onSubmit: commitValue
|
|
53
|
-
}, (form)=>/*#__PURE__*/ react.createElement(Bind, {
|
|
54
|
-
name: "previewDomain",
|
|
55
|
-
validators: [
|
|
56
|
-
validation.create("url")
|
|
57
|
-
]
|
|
58
|
-
}, /*#__PURE__*/ react.createElement(Input, {
|
|
59
|
-
autoFocus: true,
|
|
60
|
-
label: "Preview Domain",
|
|
61
|
-
description: /*#__PURE__*/ react.createElement(react.Fragment, null, "Set a custom preview domain for your session.", /*#__PURE__*/ react.createElement("br", null), "This doesn't affect other users."),
|
|
62
|
-
size: "md",
|
|
63
|
-
onBlur: ()=>form.submit(),
|
|
64
|
-
onEnter: ()=>form.submit(),
|
|
65
|
-
note: 'Hit "Enter" or click outside the menu to apply.'
|
|
66
|
-
}))), isOverridden ? /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(Separator, {
|
|
67
|
-
variant: "dimmed",
|
|
68
|
-
margin: "lg"
|
|
69
|
-
}), /*#__PURE__*/ react.createElement(Button, {
|
|
70
|
-
variant: "primary",
|
|
71
|
-
onClick: resetDomain,
|
|
72
|
-
text: "Reset Domain",
|
|
73
|
-
size: "sm"
|
|
74
|
-
}), /*#__PURE__*/ react.createElement(Separator, {
|
|
75
|
-
variant: "dimmed",
|
|
76
|
-
margin: "lg"
|
|
77
|
-
}), "Resetting will revert to using the default preview domain.") : null));
|
|
15
|
+
return /*#__PURE__*/ react.createElement(PreviewDomainMenu, {
|
|
16
|
+
className: "absolute left-0 top-0"
|
|
17
|
+
});
|
|
78
18
|
};
|
|
79
19
|
export { PreviewDomain };
|
|
80
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.js","sources":["../../../../../src/BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.tsx"],"sourcesContent":["import React, {
|
|
1
|
+
{"version":3,"file":"BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.js","sources":["../../../../../src/BaseEditor/defaultConfig/Content/AddressBar/PreviewDomain.tsx"],"sourcesContent":["import React, { useEffect } from \"react\";\nimport { PreviewDomainMenu } from \"@webiny/frontend-settings/exports/admin.js\";\nimport { usePreviewDomain } from \"../usePreviewDomain.js\";\nimport { useDocumentEditor } from \"~/DocumentEditor/index.js\";\nimport { Commands } from \"~/BaseEditor/index.js\";\n\nexport const PreviewDomain = () => {\n const editor = useDocumentEditor();\n const { previewDomain } = usePreviewDomain();\n\n useEffect(() => {\n if (!previewDomain) {\n return;\n }\n\n editor.executeCommand(Commands.RefreshPreview);\n }, [previewDomain]);\n\n return <PreviewDomainMenu className={\"absolute left-0 top-0\"} />;\n};\n"],"names":["PreviewDomain","editor","useDocumentEditor","previewDomain","usePreviewDomain","useEffect","Commands","PreviewDomainMenu"],"mappings":";;;;;AAMO,MAAMA,gBAAgB;IACzB,MAAMC,SAASC;IACf,MAAM,EAAEC,aAAa,EAAE,GAAGC;IAE1BC,UAAU;QACN,IAAI,CAACF,eACD;QAGJF,OAAO,cAAc,CAACK,SAAS,cAAc;IACjD,GAAG;QAACH;KAAc;IAElB,OAAO,WAAP,GAAO,oBAACI,mBAAiBA;QAAC,WAAW;;AACzC"}
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { useLocalStorage } from "@webiny/app";
|
|
3
|
-
import { usePreviewDomain } from "@webiny/frontend-settings/exports/admin.js";
|
|
4
|
-
const CUSTOM_PREVIEW_DOMAIN = "custom_preview_domain";
|
|
5
|
-
const usePreviewDomain_usePreviewDomain = ()=>{
|
|
6
|
-
const { previewDomain, isOverridden } = usePreviewDomain();
|
|
7
|
-
const localStorage = useLocalStorage();
|
|
8
|
-
const setPreviewDomain = useCallback((domain)=>{
|
|
9
|
-
if (domain === previewDomain) localStorage.remove(CUSTOM_PREVIEW_DOMAIN);
|
|
10
|
-
else localStorage.set(CUSTOM_PREVIEW_DOMAIN, domain);
|
|
11
|
-
}, [
|
|
12
|
-
previewDomain
|
|
13
|
-
]);
|
|
14
|
-
const unsetPreviewDomain = useCallback(()=>{
|
|
15
|
-
localStorage.remove(CUSTOM_PREVIEW_DOMAIN);
|
|
16
|
-
}, []);
|
|
17
|
-
return {
|
|
18
|
-
previewDomain,
|
|
19
|
-
setPreviewDomain,
|
|
20
|
-
unsetPreviewDomain,
|
|
21
|
-
isOverridden
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
export { usePreviewDomain_usePreviewDomain as usePreviewDomain };
|
|
25
|
-
|
|
26
|
-
//# sourceMappingURL=usePreviewDomain.js.map
|
|
1
|
+
export { usePreviewDomain } from "@webiny/frontend-settings/exports/admin.js";
|
|
@@ -54,6 +54,7 @@ const SingleInner = observer(({ value, onChange, ...props })=>{
|
|
|
54
54
|
loading: vm.loading,
|
|
55
55
|
value: vm.value,
|
|
56
56
|
options: vm.options,
|
|
57
|
+
shouldFilter: false,
|
|
57
58
|
onValueSearch: (query)=>presenter.search(query),
|
|
58
59
|
onValueChange: (selectedEntryId)=>{
|
|
59
60
|
if (!selectedEntryId) {
|
|
@@ -108,6 +109,7 @@ const MultiInner = observer(({ value, onChange, ...props })=>{
|
|
|
108
109
|
loading: vm.loading,
|
|
109
110
|
value: void 0,
|
|
110
111
|
options: vm.options.filter((option)=>!vm.values.includes(option.value)),
|
|
112
|
+
shouldFilter: false,
|
|
111
113
|
onValueSearch: (query)=>presenter.search(query),
|
|
112
114
|
onValueChange: (entryId)=>{
|
|
113
115
|
if (entryId && !vm.values.includes(entryId)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inputRenderers/ContentEntryInput.js","sources":["../../src/inputRenderers/ContentEntryInput.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { AutoComplete, Select, Input, SegmentedControl, Text } from \"@webiny/admin-ui\";\nimport type { ElementInputRendererProps } from \"~/BaseEditor/index.js\";\nimport type {\n ContentEntryInput,\n ContentEntryReference,\n ContentEntryQueryValue\n} from \"@webiny/website-builder-sdk\";\nimport { RefSingleAutocompletePresenterFeature } from \"@webiny/app-headless-cms/presentation/fieldRenderers/ref/autocomplete/single/feature.js\";\nimport { RefMultiAutocompletePresenterFeature } from \"@webiny/app-headless-cms/presentation/fieldRenderers/ref/autocomplete/multi/feature.js\";\nimport { ObjectRowActions } from \"~/inputRenderers/ObjectInput/ObjectRowActions.js\";\nimport { ObjectFieldHeader } from \"~/inputRenderers/ObjectInput/ObjectFieldHeader.js\";\n\ninterface EntryRowProps {\n title: React.ReactNode;\n actions?: React.ReactNode;\n}\n\n/**\n * A non-interactive row for a selected entry: a truncating title with\n * hover-revealed actions. Mirrors the object-list row look, without click-to-open.\n */\nconst EntryRow = ({ title, actions }: EntryRowProps) => (\n <div\n style={{ height: 40 }}\n className={\n \"group flex items-center justify-between gap-xs rounded-md border border-neutral-dimmed-darker px-sm-extra bg-neutral-base\"\n }\n >\n <div className={\"flex flex-1 items-center min-w-0\"}>\n <Text size={\"sm\"} className={\"truncate text-neutral-strong\"}>\n {title}\n </Text>\n </div>\n {actions ? (\n <div className={\"hidden group-hover:flex items-center gap-xs shrink-0\"}>{actions}</div>\n ) : null}\n </div>\n);\n\n/**\n * Editor sidebar renderer for the `contentEntry` input. Supports two modes:\n *\n * - \"manual\" (default): the editor hand-picks one (single) or several (list)\n * CMS entries from the input's author-fixed model(s), stored as references\n * (`{ id, modelId }`). Reuses the Headless CMS reference autocomplete.\n * - \"query\": the editor configures a dynamic query (sort / limit / search)\n * within the bounds the component author declared on `input.query`.\n */\nexport const ContentEntryInputRenderer = (props: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n\n if (input.mode === \"query\") {\n return <QueryInner {...props} />;\n }\n\n return <ManualRenderer {...props} />;\n};\n\n// --- Manual mode -----------------------------------------------------------\n\nconst ManualRenderer = (props: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n if (input.list) {\n RefMultiAutocompletePresenterFeature.register(child);\n } else {\n RefSingleAutocompletePresenterFeature.register(child);\n }\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n {input.list ? <MultiInner {...props} /> : <SingleInner {...props} />}\n </DiContainerProvider>\n );\n};\n\nconst SingleInner = observer(({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n const current = (value as ContentEntryReference | null | undefined) ?? null;\n\n useEffect(() => {\n presenter.init({ modelIds: input.models, value: current });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={input.label}\n description={input.description}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n onValueSearch={query => presenter.search(query)}\n onValueChange={selectedEntryId => {\n if (!selectedEntryId) {\n presenter.clear();\n onChange(({ value }) => value.set(null));\n return;\n }\n const ref = presenter.select(selectedEntryId);\n onChange(({ value }) => value.set(ref));\n }}\n onValueReset={() => {\n presenter.clear();\n onChange(({ value }) => value.set(null));\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n\nconst MultiInner = observer(({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const { presenter } = useFeature(RefMultiAutocompletePresenterFeature);\n\n const current: ContentEntryReference[] = Array.isArray(value) ? value : [];\n // Bumping this remounts the AutoComplete, clearing its search text after a pick.\n const [pickerKey, setPickerKey] = useState(0);\n\n useEffect(() => {\n presenter.init({ modelIds: input.models, values: current });\n }, []);\n\n const vm = presenter.vm;\n const labelFor = (entryId: string) =>\n vm.options.find(option => option.value === entryId)?.label ?? entryId;\n\n const setSelection = (entryIds: string[]) => {\n const refs = presenter.select(entryIds);\n onChange(({ value }) => value.set(refs.length > 0 ? refs : null));\n };\n\n const moveItem = (from: number, to: number) => {\n if (to < 0 || to >= vm.values.length) {\n return;\n }\n const next = [...vm.values];\n [next[from], next[to]] = [next[to], next[from]];\n setSelection(next);\n };\n\n return (\n <div className={\"flex flex-col gap-sm\"}>\n {/* Picker: a single autocomplete that adds the chosen entry to the list below,\n then remounts (via pickerKey) so its search input clears. */}\n <AutoComplete\n key={pickerKey}\n label={input.label}\n description={input.description}\n loading={vm.loading}\n value={undefined}\n options={vm.options.filter(option => !vm.values.includes(option.value))}\n onValueSearch={query => presenter.search(query)}\n onValueChange={entryId => {\n if (entryId && !vm.values.includes(entryId)) {\n setSelection([...vm.values, entryId]);\n setPickerKey(key => key + 1);\n }\n }}\n />\n {/* Selected entries as reorderable rows: a truncating title plus hover\n actions to move up / down / remove. */}\n {vm.values.length > 0 ? (\n <div className={\"flex flex-col gap-xs\"}>\n {vm.values.map((entryId, index) => (\n <EntryRow\n key={entryId}\n title={labelFor(entryId)}\n actions={\n <ObjectRowActions\n onMoveUp={() => moveItem(index, index - 1)}\n onMoveDown={() => moveItem(index, index + 1)}\n onRemove={() =>\n setSelection(vm.values.filter(id => id !== entryId))\n }\n canMoveUp={index > 0}\n canMoveDown={index < vm.values.length - 1}\n />\n }\n />\n ))}\n </div>\n ) : (\n <Text size={\"sm\"} className={\"text-neutral-strong\"}>\n No entries selected yet.\n </Text>\n )}\n </div>\n );\n});\n\n// --- Query mode ------------------------------------------------------------\n\nconst SORT_DIRECTION_OPTIONS = [\n { label: \"Ascending\", value: \"asc\" },\n { label: \"Descending\", value: \"desc\" }\n];\n\nconst QueryInner = ({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const config = input.query ?? {};\n const modelId = input.models[0];\n const current = (value as ContentEntryQueryValue | undefined) ?? { modelId };\n\n // Ensure modelId is always stored on the value so the frontend SDK can\n // resolve the query without component manifests.\n useEffect(() => {\n if (!current.modelId && modelId) {\n onChange(({ value: v }) => {\n v.set({ ...current, modelId });\n });\n }\n }, [modelId]);\n\n const sortFields = (config.sort?.fields ?? []).map(entry =>\n typeof entry === \"string\"\n ? { field: entry, label: entry }\n : { field: entry.field, label: entry.label ?? entry.field }\n );\n const singleSortField = sortFields.length === 1 ? sortFields[0] : null;\n const [searchText, setSearchText] = useState(current.search ?? \"\");\n\n const update = (patch: Partial<ContentEntryQueryValue>) => {\n onChange(({ value }) => {\n value.set({ ...current, modelId, ...patch });\n });\n };\n\n // Keep the field showing what the editor typed; surface an error when it's out\n // of range rather than silently rewriting the value.\n const maxLimit = config.limit?.max;\n const [limitText, setLimitText] = useState(\n String(current.limit ?? config.limit?.default ?? \"\")\n );\n\n const limitError = (() => {\n if (limitText.trim() === \"\") {\n return undefined;\n }\n const parsed = Number(limitText);\n if (!Number.isInteger(parsed)) {\n return \"Enter a whole number.\";\n }\n if (parsed < 1) {\n return \"Must be at least 1.\";\n }\n if (maxLimit && parsed > maxLimit) {\n return `Maximum is ${maxLimit}.`;\n }\n return undefined;\n })();\n\n const onLimitChange = (raw: string) => {\n setLimitText(raw);\n const parsed = Number(raw);\n // Only commit valid values — an out-of-range entry stays visible with the\n // error until it's corrected.\n if (\n raw.trim() !== \"\" &&\n Number.isInteger(parsed) &&\n parsed >= 1 &&\n (!maxLimit || parsed <= maxLimit)\n ) {\n update({ limit: parsed });\n }\n };\n\n return (\n <div className={\"flex flex-col gap-md\"}>\n {input.label ? (\n <ObjectFieldHeader label={input.label} description={input.description} />\n ) : null}\n {singleSortField ? (\n // Only one sortable field → skip the field picker; just choose direction.\n <SegmentedControl\n label={`Sort by ${singleSortField.label}`}\n value={current.sort?.order ?? \"asc\"}\n items={SORT_DIRECTION_OPTIONS}\n fullWidth\n onChange={order => {\n update({\n sort: {\n field: singleSortField.field,\n order: (order as \"asc\" | \"desc\") ?? \"asc\"\n }\n });\n }}\n />\n ) : sortFields.length > 1 ? (\n <>\n <Select\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Sort by\"}\n value={current.sort?.field}\n options={sortFields.map(field => ({\n label: field.label,\n value: field.field\n }))}\n displayResetAction\n onChange={field => {\n update({\n sort: field\n ? { field, order: current.sort?.order ?? \"asc\" }\n : undefined\n });\n }}\n />\n {current.sort?.field ? (\n <SegmentedControl\n label={\"Direction\"}\n value={current.sort?.order ?? \"asc\"}\n items={SORT_DIRECTION_OPTIONS}\n fullWidth\n onChange={order => {\n update({\n sort: {\n field: current.sort!.field,\n order: (order as \"asc\" | \"desc\") ?? \"asc\"\n }\n });\n }}\n />\n ) : null}\n </>\n ) : null}\n\n {config.limit ? (\n <Input\n type={\"number\"}\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Limit\"}\n description={maxLimit ? `Up to ${maxLimit}.` : undefined}\n value={limitText}\n validation={limitError ? { isValid: false, message: limitError } : undefined}\n onChange={onLimitChange}\n />\n ) : null}\n\n {config.search ? (\n <Input\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Search\"}\n placeholder={\"Search entries…\"}\n value={searchText}\n onChange={setSearchText}\n onBlur={e => update({ search: e.currentTarget.value })}\n onEnter={e => update({ search: e.currentTarget.value })}\n />\n ) : null}\n </div>\n );\n};\n"],"names":["EntryRow","title","actions","Text","ContentEntryInputRenderer","props","input","QueryInner","ManualRenderer","parentContainer","useContainer","scopedContainer","useMemo","child","RefMultiAutocompletePresenterFeature","RefSingleAutocompletePresenterFeature","DiContainerProvider","MultiInner","SingleInner","observer","value","onChange","presenter","useFeature","current","useEffect","vm","AutoComplete","query","selectedEntryId","ref","Array","pickerKey","setPickerKey","useState","labelFor","entryId","option","setSelection","entryIds","refs","moveItem","from","to","next","undefined","key","index","ObjectRowActions","id","SORT_DIRECTION_OPTIONS","config","modelId","v","sortFields","entry","singleSortField","searchText","setSearchText","update","patch","maxLimit","limitText","setLimitText","String","limitError","parsed","Number","onLimitChange","raw","ObjectFieldHeader","SegmentedControl","order","Select","field","Input","e"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inputRenderers/ContentEntryInput.js","sources":["../../src/inputRenderers/ContentEntryInput.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { DiContainerProvider, useContainer, useFeature } from \"@webiny/app\";\nimport { AutoComplete, Select, Input, SegmentedControl, Text } from \"@webiny/admin-ui\";\nimport type { ElementInputRendererProps } from \"~/BaseEditor/index.js\";\nimport type {\n ContentEntryInput,\n ContentEntryReference,\n ContentEntryQueryValue\n} from \"@webiny/website-builder-sdk\";\nimport { RefSingleAutocompletePresenterFeature } from \"@webiny/app-headless-cms/presentation/fieldRenderers/ref/autocomplete/single/feature.js\";\nimport { RefMultiAutocompletePresenterFeature } from \"@webiny/app-headless-cms/presentation/fieldRenderers/ref/autocomplete/multi/feature.js\";\nimport { ObjectRowActions } from \"~/inputRenderers/ObjectInput/ObjectRowActions.js\";\nimport { ObjectFieldHeader } from \"~/inputRenderers/ObjectInput/ObjectFieldHeader.js\";\n\ninterface EntryRowProps {\n title: React.ReactNode;\n actions?: React.ReactNode;\n}\n\n/**\n * A non-interactive row for a selected entry: a truncating title with\n * hover-revealed actions. Mirrors the object-list row look, without click-to-open.\n */\nconst EntryRow = ({ title, actions }: EntryRowProps) => (\n <div\n style={{ height: 40 }}\n className={\n \"group flex items-center justify-between gap-xs rounded-md border border-neutral-dimmed-darker px-sm-extra bg-neutral-base\"\n }\n >\n <div className={\"flex flex-1 items-center min-w-0\"}>\n <Text size={\"sm\"} className={\"truncate text-neutral-strong\"}>\n {title}\n </Text>\n </div>\n {actions ? (\n <div className={\"hidden group-hover:flex items-center gap-xs shrink-0\"}>{actions}</div>\n ) : null}\n </div>\n);\n\n/**\n * Editor sidebar renderer for the `contentEntry` input. Supports two modes:\n *\n * - \"manual\" (default): the editor hand-picks one (single) or several (list)\n * CMS entries from the input's author-fixed model(s), stored as references\n * (`{ id, modelId }`). Reuses the Headless CMS reference autocomplete.\n * - \"query\": the editor configures a dynamic query (sort / limit / search)\n * within the bounds the component author declared on `input.query`.\n */\nexport const ContentEntryInputRenderer = (props: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n\n if (input.mode === \"query\") {\n return <QueryInner {...props} />;\n }\n\n return <ManualRenderer {...props} />;\n};\n\n// --- Manual mode -----------------------------------------------------------\n\nconst ManualRenderer = (props: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const parentContainer = useContainer();\n\n const scopedContainer = useMemo(() => {\n const child = parentContainer.createChildContainer();\n if (input.list) {\n RefMultiAutocompletePresenterFeature.register(child);\n } else {\n RefSingleAutocompletePresenterFeature.register(child);\n }\n return child;\n }, []);\n\n return (\n <DiContainerProvider container={scopedContainer}>\n {input.list ? <MultiInner {...props} /> : <SingleInner {...props} />}\n </DiContainerProvider>\n );\n};\n\nconst SingleInner = observer(({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const { presenter } = useFeature(RefSingleAutocompletePresenterFeature);\n\n const current = (value as ContentEntryReference | null | undefined) ?? null;\n\n useEffect(() => {\n presenter.init({ modelIds: input.models, value: current });\n }, []);\n\n const vm = presenter.vm;\n\n return (\n <AutoComplete\n label={input.label}\n description={input.description}\n loading={vm.loading}\n value={vm.value}\n options={vm.options}\n shouldFilter={false}\n onValueSearch={query => presenter.search(query)}\n onValueChange={selectedEntryId => {\n if (!selectedEntryId) {\n presenter.clear();\n onChange(({ value }) => value.set(null));\n return;\n }\n const ref = presenter.select(selectedEntryId);\n onChange(({ value }) => value.set(ref));\n }}\n onValueReset={() => {\n presenter.clear();\n onChange(({ value }) => value.set(null));\n }}\n displayResetAction={vm.canReset}\n />\n );\n});\n\nconst MultiInner = observer(({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const { presenter } = useFeature(RefMultiAutocompletePresenterFeature);\n\n const current: ContentEntryReference[] = Array.isArray(value) ? value : [];\n // Bumping this remounts the AutoComplete, clearing its search text after a pick.\n const [pickerKey, setPickerKey] = useState(0);\n\n useEffect(() => {\n presenter.init({ modelIds: input.models, values: current });\n }, []);\n\n const vm = presenter.vm;\n const labelFor = (entryId: string) =>\n vm.options.find(option => option.value === entryId)?.label ?? entryId;\n\n const setSelection = (entryIds: string[]) => {\n const refs = presenter.select(entryIds);\n onChange(({ value }) => value.set(refs.length > 0 ? refs : null));\n };\n\n const moveItem = (from: number, to: number) => {\n if (to < 0 || to >= vm.values.length) {\n return;\n }\n const next = [...vm.values];\n [next[from], next[to]] = [next[to], next[from]];\n setSelection(next);\n };\n\n return (\n <div className={\"flex flex-col gap-sm\"}>\n {/* Picker: a single autocomplete that adds the chosen entry to the list below,\n then remounts (via pickerKey) so its search input clears. */}\n <AutoComplete\n key={pickerKey}\n label={input.label}\n description={input.description}\n loading={vm.loading}\n value={undefined}\n options={vm.options.filter(option => !vm.values.includes(option.value))}\n shouldFilter={false}\n onValueSearch={query => presenter.search(query)}\n onValueChange={entryId => {\n if (entryId && !vm.values.includes(entryId)) {\n setSelection([...vm.values, entryId]);\n setPickerKey(key => key + 1);\n }\n }}\n />\n {/* Selected entries as reorderable rows: a truncating title plus hover\n actions to move up / down / remove. */}\n {vm.values.length > 0 ? (\n <div className={\"flex flex-col gap-xs\"}>\n {vm.values.map((entryId, index) => (\n <EntryRow\n key={entryId}\n title={labelFor(entryId)}\n actions={\n <ObjectRowActions\n onMoveUp={() => moveItem(index, index - 1)}\n onMoveDown={() => moveItem(index, index + 1)}\n onRemove={() =>\n setSelection(vm.values.filter(id => id !== entryId))\n }\n canMoveUp={index > 0}\n canMoveDown={index < vm.values.length - 1}\n />\n }\n />\n ))}\n </div>\n ) : (\n <Text size={\"sm\"} className={\"text-neutral-strong\"}>\n No entries selected yet.\n </Text>\n )}\n </div>\n );\n});\n\n// --- Query mode ------------------------------------------------------------\n\nconst SORT_DIRECTION_OPTIONS = [\n { label: \"Ascending\", value: \"asc\" },\n { label: \"Descending\", value: \"desc\" }\n];\n\nconst QueryInner = ({ value, onChange, ...props }: ElementInputRendererProps) => {\n const input = props.input as ContentEntryInput;\n const config = input.query ?? {};\n const modelId = input.models[0];\n const current = (value as ContentEntryQueryValue | undefined) ?? { modelId };\n\n // Ensure modelId is always stored on the value so the frontend SDK can\n // resolve the query without component manifests.\n useEffect(() => {\n if (!current.modelId && modelId) {\n onChange(({ value: v }) => {\n v.set({ ...current, modelId });\n });\n }\n }, [modelId]);\n\n const sortFields = (config.sort?.fields ?? []).map(entry =>\n typeof entry === \"string\"\n ? { field: entry, label: entry }\n : { field: entry.field, label: entry.label ?? entry.field }\n );\n const singleSortField = sortFields.length === 1 ? sortFields[0] : null;\n const [searchText, setSearchText] = useState(current.search ?? \"\");\n\n const update = (patch: Partial<ContentEntryQueryValue>) => {\n onChange(({ value }) => {\n value.set({ ...current, modelId, ...patch });\n });\n };\n\n // Keep the field showing what the editor typed; surface an error when it's out\n // of range rather than silently rewriting the value.\n const maxLimit = config.limit?.max;\n const [limitText, setLimitText] = useState(\n String(current.limit ?? config.limit?.default ?? \"\")\n );\n\n const limitError = (() => {\n if (limitText.trim() === \"\") {\n return undefined;\n }\n const parsed = Number(limitText);\n if (!Number.isInteger(parsed)) {\n return \"Enter a whole number.\";\n }\n if (parsed < 1) {\n return \"Must be at least 1.\";\n }\n if (maxLimit && parsed > maxLimit) {\n return `Maximum is ${maxLimit}.`;\n }\n return undefined;\n })();\n\n const onLimitChange = (raw: string) => {\n setLimitText(raw);\n const parsed = Number(raw);\n // Only commit valid values — an out-of-range entry stays visible with the\n // error until it's corrected.\n if (\n raw.trim() !== \"\" &&\n Number.isInteger(parsed) &&\n parsed >= 1 &&\n (!maxLimit || parsed <= maxLimit)\n ) {\n update({ limit: parsed });\n }\n };\n\n return (\n <div className={\"flex flex-col gap-md\"}>\n {input.label ? (\n <ObjectFieldHeader label={input.label} description={input.description} />\n ) : null}\n {singleSortField ? (\n // Only one sortable field → skip the field picker; just choose direction.\n <SegmentedControl\n label={`Sort by ${singleSortField.label}`}\n value={current.sort?.order ?? \"asc\"}\n items={SORT_DIRECTION_OPTIONS}\n fullWidth\n onChange={order => {\n update({\n sort: {\n field: singleSortField.field,\n order: (order as \"asc\" | \"desc\") ?? \"asc\"\n }\n });\n }}\n />\n ) : sortFields.length > 1 ? (\n <>\n <Select\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Sort by\"}\n value={current.sort?.field}\n options={sortFields.map(field => ({\n label: field.label,\n value: field.field\n }))}\n displayResetAction\n onChange={field => {\n update({\n sort: field\n ? { field, order: current.sort?.order ?? \"asc\" }\n : undefined\n });\n }}\n />\n {current.sort?.field ? (\n <SegmentedControl\n label={\"Direction\"}\n value={current.sort?.order ?? \"asc\"}\n items={SORT_DIRECTION_OPTIONS}\n fullWidth\n onChange={order => {\n update({\n sort: {\n field: current.sort!.field,\n order: (order as \"asc\" | \"desc\") ?? \"asc\"\n }\n });\n }}\n />\n ) : null}\n </>\n ) : null}\n\n {config.limit ? (\n <Input\n type={\"number\"}\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Limit\"}\n description={maxLimit ? `Up to ${maxLimit}.` : undefined}\n value={limitText}\n validation={limitError ? { isValid: false, message: limitError } : undefined}\n onChange={onLimitChange}\n />\n ) : null}\n\n {config.search ? (\n <Input\n size={\"md\"}\n variant={\"secondary\"}\n label={\"Search\"}\n placeholder={\"Search entries…\"}\n value={searchText}\n onChange={setSearchText}\n onBlur={e => update({ search: e.currentTarget.value })}\n onEnter={e => update({ search: e.currentTarget.value })}\n />\n ) : null}\n </div>\n );\n};\n"],"names":["EntryRow","title","actions","Text","ContentEntryInputRenderer","props","input","QueryInner","ManualRenderer","parentContainer","useContainer","scopedContainer","useMemo","child","RefMultiAutocompletePresenterFeature","RefSingleAutocompletePresenterFeature","DiContainerProvider","MultiInner","SingleInner","observer","value","onChange","presenter","useFeature","current","useEffect","vm","AutoComplete","query","selectedEntryId","ref","Array","pickerKey","setPickerKey","useState","labelFor","entryId","option","setSelection","entryIds","refs","moveItem","from","to","next","undefined","key","index","ObjectRowActions","id","SORT_DIRECTION_OPTIONS","config","modelId","v","sortFields","entry","singleSortField","searchText","setSearchText","update","patch","maxLimit","limitText","setLimitText","String","limitError","parsed","Number","onLimitChange","raw","ObjectFieldHeader","SegmentedControl","order","Select","field","Input","e"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/app-website-builder",
|
|
3
|
-
"version": "6.5.0
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -19,28 +19,28 @@
|
|
|
19
19
|
"@lexical/utils": "0.52.0",
|
|
20
20
|
"@monaco-editor/react": "4.7.0",
|
|
21
21
|
"@types/react": "18.3.31",
|
|
22
|
-
"@webiny/admin-ui": "6.5.0
|
|
23
|
-
"@webiny/app": "6.5.0
|
|
24
|
-
"@webiny/app-aco": "6.5.0
|
|
25
|
-
"@webiny/app-admin": "6.5.0
|
|
26
|
-
"@webiny/app-headless-cms": "6.5.0
|
|
27
|
-
"@webiny/app-headless-cms-common": "6.5.0
|
|
28
|
-
"@webiny/app-utils": "6.5.0
|
|
22
|
+
"@webiny/admin-ui": "6.5.0",
|
|
23
|
+
"@webiny/app": "6.5.0",
|
|
24
|
+
"@webiny/app-aco": "6.5.0",
|
|
25
|
+
"@webiny/app-admin": "6.5.0",
|
|
26
|
+
"@webiny/app-headless-cms": "6.5.0",
|
|
27
|
+
"@webiny/app-headless-cms-common": "6.5.0",
|
|
28
|
+
"@webiny/app-utils": "6.5.0",
|
|
29
29
|
"@webiny/di": "1.1.0",
|
|
30
|
-
"@webiny/feature": "6.5.0
|
|
31
|
-
"@webiny/form": "6.5.0
|
|
32
|
-
"@webiny/frontend-settings": "6.5.0
|
|
33
|
-
"@webiny/icons": "6.5.0
|
|
34
|
-
"@webiny/languages": "6.5.0
|
|
35
|
-
"@webiny/lexical-converter": "6.5.0
|
|
36
|
-
"@webiny/lexical-editor": "6.5.0
|
|
37
|
-
"@webiny/lexical-nodes": "6.5.0
|
|
38
|
-
"@webiny/lexical-theme": "6.5.0
|
|
39
|
-
"@webiny/react-composition": "6.5.0
|
|
40
|
-
"@webiny/react-properties": "6.5.0
|
|
41
|
-
"@webiny/utils": "6.5.0
|
|
42
|
-
"@webiny/validation": "6.5.0
|
|
43
|
-
"@webiny/website-builder-sdk": "6.5.0
|
|
30
|
+
"@webiny/feature": "6.5.0",
|
|
31
|
+
"@webiny/form": "6.5.0",
|
|
32
|
+
"@webiny/frontend-settings": "6.5.0",
|
|
33
|
+
"@webiny/icons": "6.5.0",
|
|
34
|
+
"@webiny/languages": "6.5.0",
|
|
35
|
+
"@webiny/lexical-converter": "6.5.0",
|
|
36
|
+
"@webiny/lexical-editor": "6.5.0",
|
|
37
|
+
"@webiny/lexical-nodes": "6.5.0",
|
|
38
|
+
"@webiny/lexical-theme": "6.5.0",
|
|
39
|
+
"@webiny/react-composition": "6.5.0",
|
|
40
|
+
"@webiny/react-properties": "6.5.0",
|
|
41
|
+
"@webiny/utils": "6.5.0",
|
|
42
|
+
"@webiny/validation": "6.5.0",
|
|
43
|
+
"@webiny/website-builder-sdk": "6.5.0",
|
|
44
44
|
"classnames": "2.5.1",
|
|
45
45
|
"deep-equal": "2.2.3",
|
|
46
46
|
"graphql": "16.14.2",
|
|
@@ -50,22 +50,22 @@
|
|
|
50
50
|
"mobx": "7.0.6",
|
|
51
51
|
"mobx-react-lite": "5.1.0",
|
|
52
52
|
"pluralize": "8.0.0",
|
|
53
|
-
"radix-ui": "1.
|
|
53
|
+
"radix-ui": "1.8.1",
|
|
54
54
|
"react": "18.3.1",
|
|
55
55
|
"react-dnd": "16.0.1",
|
|
56
56
|
"react-dnd-html5-backend": "16.0.1",
|
|
57
57
|
"react-dom": "18.3.1",
|
|
58
58
|
"slugify": "1.6.9",
|
|
59
|
-
"zod": "4.6.
|
|
59
|
+
"zod": "4.6.7"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/deep-equal": "1.0.4",
|
|
63
63
|
"@types/is-hotkey": "0.1.10",
|
|
64
|
-
"@webiny/build-tools": "6.5.0
|
|
64
|
+
"@webiny/build-tools": "6.5.0",
|
|
65
65
|
"execa": "5.1.1",
|
|
66
66
|
"rimraf": "6.1.3",
|
|
67
67
|
"typescript": "7.0.2",
|
|
68
|
-
"vitest": "5.0.
|
|
68
|
+
"vitest": "5.0.5"
|
|
69
69
|
},
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import type { PageRevision } from "../../../../domain/PageRevision/index.js";
|
|
2
3
|
import type { RevisionListPresenter } from "../../../../presentation/pages/RevisionList/abstractions.js";
|
|
3
4
|
interface RevisionListItemProps {
|
|
4
5
|
item: RevisionListPresenter.ItemViewModel;
|
|
6
|
+
onDeleted?: (revision: PageRevision) => void;
|
|
5
7
|
}
|
|
6
|
-
declare const RevisionListItem: ({ item }: RevisionListItemProps) => React.JSX.Element;
|
|
8
|
+
declare const RevisionListItem: ({ item, onDeleted }: RevisionListItemProps) => React.JSX.Element;
|
|
7
9
|
export default RevisionListItem;
|
|
@@ -58,10 +58,11 @@ const getIcon = (revision)=>{
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
|
-
const RevisionListItem = ({ item })=>{
|
|
61
|
+
const RevisionListItem = ({ item, onDeleted })=>{
|
|
62
62
|
const { revision } = item;
|
|
63
63
|
const { createRevision, editRevision, deleteRevision, unpublishRevision, publishRevision } = useRevision({
|
|
64
|
-
revision
|
|
64
|
+
revision,
|
|
65
|
+
onDeleted
|
|
65
66
|
});
|
|
66
67
|
const { icon, text: tooltipText } = getIcon(revision);
|
|
67
68
|
return /*#__PURE__*/ react.createElement(List.Item, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/pages/PageEditor/Revisions/RevisionListItem.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/RevisionListItem.tsx"],"sourcesContent":["import React from \"react\";\nconst DateDisplay = ({ date }: { date: string }) => {\n const formatted = React.useMemo(() => {\n const d = new globalThis.Date(date);\n if (isNaN(d.getTime())) {\n return \"\";\n }\n return new Intl.DateTimeFormat(navigator.language, {\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\"\n }).format(d);\n }, [date]);\n return <>{formatted}</>;\n};\nimport { DropdownMenu, Icon, IconButton, List, Text, Tooltip } from \"@webiny/admin-ui\";\nimport { ReactComponent as MoreVerticalIcon } from \"@webiny/icons/more_vert.svg\";\nimport { ReactComponent as LockIcon } from \"@webiny/icons/lock.svg\";\nimport { ReactComponent as BeenHereIcon } from \"@webiny/icons/beenhere.svg\";\nimport { ReactComponent as GestureIcon } from \"@webiny/icons/gesture.svg\";\nimport { ReactComponent as AddIcon } from \"@webiny/icons/add.svg\";\nimport { ReactComponent as EditIcon } from \"@webiny/icons/edit.svg\";\nimport { ReactComponent as PublishIcon } from \"@webiny/icons/visibility.svg\";\nimport { ReactComponent as UnpublishIcon } from \"@webiny/icons/visibility_off.svg\";\nimport { ReactComponent as DeleteIcon } from \"@webiny/icons/delete.svg\";\nimport { useRevision } from \"./useRevision.js\";\nimport type { PageRevision } from \"~/domain/PageRevision/index.js\";\nimport { i18n } from \"@webiny/app/i18n/index.js\";\nimport type { RevisionListPresenter } from \"~/presentation/pages/RevisionList/abstractions.js\";\n\nconst t = i18n.ns(\"app-website-builder/modules/Pages/PageEditor/Revisions\");\n\nconst getIcon = (revision: PageRevision) => {\n switch (true) {\n case revision.locked && revision.status !== \"published\":\n return {\n icon: (\n <Icon\n label={\"Locked revision\"}\n icon={<LockIcon />}\n data-testid={\"page.revision.status.locked\"}\n />\n ),\n text: \"This revision is locked (it has already been published)\"\n };\n case revision.status === \"published\":\n return {\n icon: (\n <Icon\n label=\"Published revision\"\n icon={<BeenHereIcon />}\n color={\"accent\"}\n data-testid={\"page.revision.status.published\"}\n />\n ),\n text: \"This revision is currently published!\"\n };\n default:\n return {\n icon: (\n <Icon\n label={\"Draft revision\"}\n icon={<GestureIcon />}\n data-testid={\"page.revision.status.draft\"}\n />\n ),\n text: \"This is a draft\"\n };\n }\n};\n\ninterface RevisionListItemProps {\n item: RevisionListPresenter.ItemViewModel;\n}\n\nconst RevisionListItem = ({ item }: RevisionListItemProps) => {\n const { revision } = item;\n const { createRevision, editRevision, deleteRevision, unpublishRevision, publishRevision } =\n useRevision({\n revision\n });\n\n const { icon, text: tooltipText } = getIcon(revision);\n\n return (\n <List.Item\n icon={<Tooltip content={tooltipText} trigger={icon} />}\n title={revision.title || t`N/A`}\n description={\n <>\n {revision.revisionDescription ? (\n <Text as={\"div\"} size={\"md\"} className={\"mb-2\"}>\n {revision.revisionDescription}\n </Text>\n ) : null}\n <Text as={\"div\"} size={\"sm\"}>\n {t`Last modified by {author} on {time} (#{version})`({\n // Added this because revisionCreatedBy can be returned as null from GraphQL.\n author: revision.createdBy?.displayName,\n time: <DateDisplay date={revision.savedOn} />,\n version: revision.version\n })}\n </Text>\n </>\n }\n about={revision.revisionDescription}\n actions={\n <DropdownMenu\n trigger={\n <IconButton\n variant={\"ghost\"}\n size={\"sm\"}\n iconSize={\"lg\"}\n icon={<MoreVerticalIcon />}\n />\n }\n data-testid={\"page.revisions.more-options\"}\n >\n <>\n {item.canCreateFrom ? (\n <DropdownMenu.Item\n onClick={() => createRevision()}\n data-testid={\"page.revisioncreate-revision\"}\n icon={<AddIcon />}\n text={t`New revision from current`}\n />\n ) : null}\n\n {item.canEdit ? (\n <DropdownMenu.Item\n onClick={() => {\n editRevision();\n }}\n icon={<EditIcon />}\n text={t`Edit revision`}\n />\n ) : null}\n\n {item.canPublish ? (\n <DropdownMenu.Item\n onClick={() => publishRevision()}\n data-testid={\"page.revisionpublish\"}\n icon={<PublishIcon />}\n text={t`Publish revision`}\n />\n ) : null}\n\n {item.canUnpublish ? (\n <DropdownMenu.Item\n onClick={() => unpublishRevision()}\n data-testid={\"page.revisionunpublish\"}\n icon={<UnpublishIcon />}\n text={t`Unpublish revision`}\n />\n ) : null}\n\n {item.canDelete ? (\n <>\n <DropdownMenu.Separator />\n <DropdownMenu.Item\n onClick={() => deleteRevision()}\n icon={<DeleteIcon />}\n text={t`Delete revision`}\n variant={\"destructive\"}\n />\n </>\n ) : null}\n </>\n </DropdownMenu>\n }\n />\n );\n};\n\nexport default RevisionListItem;\n"],"names":["DateDisplay","date","formatted","React","d","globalThis","isNaN","Intl","navigator","t","i18n","getIcon","revision","Icon","LockIcon","BeenHereIcon","GestureIcon","RevisionListItem","item","createRevision","editRevision","deleteRevision","unpublishRevision","publishRevision","useRevision","icon","tooltipText","List","Tooltip","Text","DropdownMenu","IconButton","MoreVerticalIcon","AddIcon","EditIcon","PublishIcon","UnpublishIcon","DeleteIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"presentation/pages/PageEditor/Revisions/RevisionListItem.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/RevisionListItem.tsx"],"sourcesContent":["import React from \"react\";\nconst DateDisplay = ({ date }: { date: string }) => {\n const formatted = React.useMemo(() => {\n const d = new globalThis.Date(date);\n if (isNaN(d.getTime())) {\n return \"\";\n }\n return new Intl.DateTimeFormat(navigator.language, {\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\"\n }).format(d);\n }, [date]);\n return <>{formatted}</>;\n};\nimport { DropdownMenu, Icon, IconButton, List, Text, Tooltip } from \"@webiny/admin-ui\";\nimport { ReactComponent as MoreVerticalIcon } from \"@webiny/icons/more_vert.svg\";\nimport { ReactComponent as LockIcon } from \"@webiny/icons/lock.svg\";\nimport { ReactComponent as BeenHereIcon } from \"@webiny/icons/beenhere.svg\";\nimport { ReactComponent as GestureIcon } from \"@webiny/icons/gesture.svg\";\nimport { ReactComponent as AddIcon } from \"@webiny/icons/add.svg\";\nimport { ReactComponent as EditIcon } from \"@webiny/icons/edit.svg\";\nimport { ReactComponent as PublishIcon } from \"@webiny/icons/visibility.svg\";\nimport { ReactComponent as UnpublishIcon } from \"@webiny/icons/visibility_off.svg\";\nimport { ReactComponent as DeleteIcon } from \"@webiny/icons/delete.svg\";\nimport { useRevision } from \"./useRevision.js\";\nimport type { PageRevision } from \"~/domain/PageRevision/index.js\";\nimport { i18n } from \"@webiny/app/i18n/index.js\";\nimport type { RevisionListPresenter } from \"~/presentation/pages/RevisionList/abstractions.js\";\n\nconst t = i18n.ns(\"app-website-builder/modules/Pages/PageEditor/Revisions\");\n\nconst getIcon = (revision: PageRevision) => {\n switch (true) {\n case revision.locked && revision.status !== \"published\":\n return {\n icon: (\n <Icon\n label={\"Locked revision\"}\n icon={<LockIcon />}\n data-testid={\"page.revision.status.locked\"}\n />\n ),\n text: \"This revision is locked (it has already been published)\"\n };\n case revision.status === \"published\":\n return {\n icon: (\n <Icon\n label=\"Published revision\"\n icon={<BeenHereIcon />}\n color={\"accent\"}\n data-testid={\"page.revision.status.published\"}\n />\n ),\n text: \"This revision is currently published!\"\n };\n default:\n return {\n icon: (\n <Icon\n label={\"Draft revision\"}\n icon={<GestureIcon />}\n data-testid={\"page.revision.status.draft\"}\n />\n ),\n text: \"This is a draft\"\n };\n }\n};\n\ninterface RevisionListItemProps {\n item: RevisionListPresenter.ItemViewModel;\n onDeleted?: (revision: PageRevision) => void;\n}\n\nconst RevisionListItem = ({ item, onDeleted }: RevisionListItemProps) => {\n const { revision } = item;\n const { createRevision, editRevision, deleteRevision, unpublishRevision, publishRevision } =\n useRevision({\n revision,\n onDeleted\n });\n\n const { icon, text: tooltipText } = getIcon(revision);\n\n return (\n <List.Item\n icon={<Tooltip content={tooltipText} trigger={icon} />}\n title={revision.title || t`N/A`}\n description={\n <>\n {revision.revisionDescription ? (\n <Text as={\"div\"} size={\"md\"} className={\"mb-2\"}>\n {revision.revisionDescription}\n </Text>\n ) : null}\n <Text as={\"div\"} size={\"sm\"}>\n {t`Last modified by {author} on {time} (#{version})`({\n // Added this because revisionCreatedBy can be returned as null from GraphQL.\n author: revision.createdBy?.displayName,\n time: <DateDisplay date={revision.savedOn} />,\n version: revision.version\n })}\n </Text>\n </>\n }\n about={revision.revisionDescription}\n actions={\n <DropdownMenu\n trigger={\n <IconButton\n variant={\"ghost\"}\n size={\"sm\"}\n iconSize={\"lg\"}\n icon={<MoreVerticalIcon />}\n />\n }\n data-testid={\"page.revisions.more-options\"}\n >\n <>\n {item.canCreateFrom ? (\n <DropdownMenu.Item\n onClick={() => createRevision()}\n data-testid={\"page.revisioncreate-revision\"}\n icon={<AddIcon />}\n text={t`New revision from current`}\n />\n ) : null}\n\n {item.canEdit ? (\n <DropdownMenu.Item\n onClick={() => {\n editRevision();\n }}\n icon={<EditIcon />}\n text={t`Edit revision`}\n />\n ) : null}\n\n {item.canPublish ? (\n <DropdownMenu.Item\n onClick={() => publishRevision()}\n data-testid={\"page.revisionpublish\"}\n icon={<PublishIcon />}\n text={t`Publish revision`}\n />\n ) : null}\n\n {item.canUnpublish ? (\n <DropdownMenu.Item\n onClick={() => unpublishRevision()}\n data-testid={\"page.revisionunpublish\"}\n icon={<UnpublishIcon />}\n text={t`Unpublish revision`}\n />\n ) : null}\n\n {item.canDelete ? (\n <>\n <DropdownMenu.Separator />\n <DropdownMenu.Item\n onClick={() => deleteRevision()}\n icon={<DeleteIcon />}\n text={t`Delete revision`}\n variant={\"destructive\"}\n />\n </>\n ) : null}\n </>\n </DropdownMenu>\n }\n />\n );\n};\n\nexport default RevisionListItem;\n"],"names":["DateDisplay","date","formatted","React","d","globalThis","isNaN","Intl","navigator","t","i18n","getIcon","revision","Icon","LockIcon","BeenHereIcon","GestureIcon","RevisionListItem","item","onDeleted","createRevision","editRevision","deleteRevision","unpublishRevision","publishRevision","useRevision","icon","tooltipText","List","Tooltip","Text","DropdownMenu","IconButton","MoreVerticalIcon","AddIcon","EditIcon","PublishIcon","UnpublishIcon","DeleteIcon"],"mappings":"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"}
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
-
import react from "react";
|
|
1
|
+
import react, { useCallback } from "react";
|
|
2
2
|
import { List, OverlayLoader } from "@webiny/admin-ui";
|
|
3
|
+
import { useRouter } from "@webiny/app";
|
|
3
4
|
import RevisionListItem from "./RevisionListItem.js";
|
|
5
|
+
import { Routes } from "../../../../routes.js";
|
|
4
6
|
import { useRevisionList } from "./useRevisionList.js";
|
|
7
|
+
import { usePageEditorDrawer } from "./usePageEditorDrawer.js";
|
|
5
8
|
const RevisionsList = (props)=>{
|
|
6
9
|
const { page } = props;
|
|
7
10
|
const { vm } = useRevisionList(page.id);
|
|
11
|
+
const { goToRoute } = useRouter();
|
|
12
|
+
const { openRevisionList } = usePageEditorDrawer();
|
|
13
|
+
const onDeleted = useCallback((revision)=>{
|
|
14
|
+
if (revision.id !== page.id) return;
|
|
15
|
+
openRevisionList(false);
|
|
16
|
+
const next = vm.revisions.find((item)=>item.revision.id !== revision.id);
|
|
17
|
+
if (!next) return void goToRoute(Routes.Pages.List);
|
|
18
|
+
goToRoute(Routes.Pages.Editor, {
|
|
19
|
+
id: next.revision.id
|
|
20
|
+
});
|
|
21
|
+
}, [
|
|
22
|
+
page.id,
|
|
23
|
+
vm.revisions,
|
|
24
|
+
openRevisionList
|
|
25
|
+
]);
|
|
8
26
|
return /*#__PURE__*/ react.createElement("div", {
|
|
9
27
|
className: "relative"
|
|
10
28
|
}, (vm.isLoading || vm.isMutating) && /*#__PURE__*/ react.createElement(OverlayLoader, null), vm.revisions.length ? /*#__PURE__*/ react.createElement(List, {
|
|
11
29
|
"data-testid": "cms.content-form.revisions"
|
|
12
30
|
}, vm.revisions.map((item)=>/*#__PURE__*/ react.createElement(RevisionListItem, {
|
|
13
31
|
item: item,
|
|
14
|
-
key: item.revision.id
|
|
32
|
+
key: item.revision.id,
|
|
33
|
+
onDeleted: onDeleted
|
|
15
34
|
}))) : /*#__PURE__*/ react.createElement("div", {
|
|
16
35
|
className: "p-lg"
|
|
17
36
|
}, "No revisions to show."));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/pages/PageEditor/Revisions/RevisionsList.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/RevisionsList.tsx"],"sourcesContent":["import React from \"react\";\nimport { List, OverlayLoader } from \"@webiny/admin-ui\";\nimport RevisionListItem from \"./RevisionListItem.js\";\nimport type { Page } from \"~/domain/Page/index.js\";\nimport { useRevisionList } from \"./useRevisionList.js\";\n\ninterface IRevisionsListProps {\n page: Pick<Page, \"id\">;\n}\n\nexport const RevisionsList = (props: IRevisionsListProps) => {\n const { page } = props;\n const { vm } = useRevisionList(page.id);\n\n return (\n <div className={\"relative\"}>\n {(vm.isLoading || vm.isMutating) && <OverlayLoader />}\n {vm.revisions.length ? (\n <List data-testid={\"cms.content-form.revisions\"}>\n {vm.revisions.map(item => (\n <RevisionListItem
|
|
1
|
+
{"version":3,"file":"presentation/pages/PageEditor/Revisions/RevisionsList.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/RevisionsList.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { List, OverlayLoader } from \"@webiny/admin-ui\";\nimport { useRouter } from \"@webiny/app\";\nimport RevisionListItem from \"./RevisionListItem.js\";\nimport type { Page } from \"~/domain/Page/index.js\";\nimport type { PageRevision } from \"~/domain/PageRevision/index.js\";\nimport { Routes } from \"~/routes.js\";\nimport { useRevisionList } from \"./useRevisionList.js\";\nimport { usePageEditorDrawer } from \"./usePageEditorDrawer.js\";\n\ninterface IRevisionsListProps {\n page: Pick<Page, \"id\">;\n}\n\nexport const RevisionsList = (props: IRevisionsListProps) => {\n const { page } = props;\n const { vm } = useRevisionList(page.id);\n const { goToRoute } = useRouter();\n const { openRevisionList } = usePageEditorDrawer();\n\n /**\n * Deleting the revision that is open in the editor would leave the editor on a revision that\n * no longer exists, so we move to the newest remaining one, or back to the list if none is left.\n */\n const onDeleted = useCallback(\n (revision: PageRevision) => {\n if (revision.id !== page.id) {\n return;\n }\n\n openRevisionList(false);\n\n const next = vm.revisions.find(item => item.revision.id !== revision.id);\n if (!next) {\n goToRoute(Routes.Pages.List);\n return;\n }\n\n goToRoute(Routes.Pages.Editor, {\n id: next.revision.id\n });\n },\n [page.id, vm.revisions, openRevisionList]\n );\n\n return (\n <div className={\"relative\"}>\n {(vm.isLoading || vm.isMutating) && <OverlayLoader />}\n {vm.revisions.length ? (\n <List data-testid={\"cms.content-form.revisions\"}>\n {vm.revisions.map(item => (\n <RevisionListItem\n item={item}\n key={item.revision.id}\n onDeleted={onDeleted}\n />\n ))}\n </List>\n ) : (\n <div className={\"p-lg\"}>No revisions to show.</div>\n )}\n </div>\n );\n};\n"],"names":["RevisionsList","props","page","vm","useRevisionList","goToRoute","useRouter","openRevisionList","usePageEditorDrawer","onDeleted","useCallback","revision","next","item","Routes","OverlayLoader","List","RevisionListItem"],"mappings":";;;;;;;AAcO,MAAMA,gBAAgB,CAACC;IAC1B,MAAM,EAAEC,IAAI,EAAE,GAAGD;IACjB,MAAM,EAAEE,EAAE,EAAE,GAAGC,gBAAgBF,KAAK,EAAE;IACtC,MAAM,EAAEG,SAAS,EAAE,GAAGC;IACtB,MAAM,EAAEC,gBAAgB,EAAE,GAAGC;IAM7B,MAAMC,YAAYC,YACd,CAACC;QACG,IAAIA,SAAS,EAAE,KAAKT,KAAK,EAAE,EACvB;QAGJK,iBAAiB;QAEjB,MAAMK,OAAOT,GAAG,SAAS,CAAC,IAAI,CAACU,CAAAA,OAAQA,KAAK,QAAQ,CAAC,EAAE,KAAKF,SAAS,EAAE;QACvE,IAAI,CAACC,MAAM,YACPP,UAAUS,OAAO,KAAK,CAAC,IAAI;QAI/BT,UAAUS,OAAO,KAAK,CAAC,MAAM,EAAE;YAC3B,IAAIF,KAAK,QAAQ,CAAC,EAAE;QACxB;IACJ,GACA;QAACV,KAAK,EAAE;QAAEC,GAAG,SAAS;QAAEI;KAAiB;IAG7C,OAAO,WAAP,GACI,oBAAC;QAAI,WAAW;OACVJ,AAAAA,CAAAA,GAAG,SAAS,IAAIA,GAAG,UAAS,mBAAM,oBAACY,eAAaA,OACjDZ,GAAG,SAAS,CAAC,MAAM,GAAG,WAAH,GAChB,oBAACa,MAAIA;QAAC,eAAa;OACdb,GAAG,SAAS,CAAC,GAAG,CAACU,CAAAA,OAAAA,WAAAA,GACd,oBAACI,kBAAgBA;YACb,MAAMJ;YACN,KAAKA,KAAK,QAAQ,CAAC,EAAE;YACrB,WAAWJ;6BAKvB,oBAAC;QAAI,WAAW;OAAQ;AAIxC"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { PageRevision } from "../../../../domain/PageRevision/index.js";
|
|
2
2
|
export interface UseRevisionProps {
|
|
3
3
|
revision: PageRevision;
|
|
4
|
+
/**
|
|
5
|
+
* Called once the revision is deleted, so the list can move the editor away from a revision
|
|
6
|
+
* that no longer exists.
|
|
7
|
+
*/
|
|
8
|
+
onDeleted?: (revision: PageRevision) => void;
|
|
4
9
|
}
|
|
5
10
|
export declare const useRevision: (props: UseRevisionProps) => {
|
|
6
11
|
createRevision: () => Promise<void>;
|
|
@@ -9,7 +9,7 @@ import { Routes } from "../../../../routes.js";
|
|
|
9
9
|
import { useRouter } from "@webiny/app";
|
|
10
10
|
import { usePageEditorDrawer } from "./usePageEditorDrawer.js";
|
|
11
11
|
const useRevision = (props)=>{
|
|
12
|
-
const { revision } = props;
|
|
12
|
+
const { revision, onDeleted } = props;
|
|
13
13
|
const { goToRoute } = useRouter();
|
|
14
14
|
const { openRevisionList } = usePageEditorDrawer();
|
|
15
15
|
const { showDialog } = useDialogs();
|
|
@@ -40,6 +40,7 @@ const useRevision = (props)=>{
|
|
|
40
40
|
showSuccessToast({
|
|
41
41
|
title: `Revision #${revision.version} was deleted successfully!`
|
|
42
42
|
});
|
|
43
|
+
onDeleted?.(revision);
|
|
43
44
|
} catch (ex) {
|
|
44
45
|
showWarningToast({
|
|
45
46
|
title: "Could not delete the revision.",
|
|
@@ -50,7 +51,8 @@ const useRevision = (props)=>{
|
|
|
50
51
|
});
|
|
51
52
|
}, [
|
|
52
53
|
revision.id,
|
|
53
|
-
revision.version
|
|
54
|
+
revision.version,
|
|
55
|
+
onDeleted
|
|
54
56
|
]);
|
|
55
57
|
const publishRevision = useCallback(()=>{
|
|
56
58
|
showDialog({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/pages/PageEditor/Revisions/useRevision.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/useRevision.tsx"],"sourcesContent":["import React from \"react\";\nimport type { PageRevision } from \"~/domain/PageRevision/index.js\";\nimport {\n usePublishPage,\n useUnpublishPage,\n useCreatePageRevisionFrom,\n useDeletePageRevision\n} from \"~/features/pages/index.js\";\nimport { useCallback } from \"react\";\nimport { Text, useToast } from \"@webiny/admin-ui\";\nimport { useDialogs } from \"@webiny/app-admin\";\nimport { ReactComponent as PublishIcon } from \"@webiny/icons/visibility.svg\";\nimport { ReactComponent as UnpublishIcon } from \"@webiny/icons/visibility_off.svg\";\nimport { ReactComponent as DeleteIcon } from \"@webiny/icons/delete.svg\";\nimport { Routes } from \"~/routes.js\";\nimport { useRouter } from \"@webiny/app\";\nimport { usePageEditorDrawer } from \"./usePageEditorDrawer.js\";\n\nexport interface UseRevisionProps {\n revision: PageRevision;\n}\n\nexport const useRevision = (props: UseRevisionProps) => {\n const { revision } = props;\n\n const { goToRoute } = useRouter();\n const { openRevisionList } = usePageEditorDrawer();\n const { showDialog } = useDialogs();\n const { showSuccessToast, showWarningToast } = useToast();\n\n const { createPageRevisionFrom } = useCreatePageRevisionFrom();\n const { deletePageRevision } = useDeletePageRevision();\n const { publishPage } = usePublishPage();\n const { unpublishPage } = useUnpublishPage();\n\n const createRevision = useCallback(async () => {\n await createPageRevisionFrom({\n id: revision.id\n });\n }, [revision.id]);\n\n const deleteRevision = useCallback(() => {\n showDialog({\n title: \"Delete revision\",\n icon: <DeleteIcon />,\n content: (\n <Text>\n {`You are about to delete revision #${revision.version}. This cannot be undone. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, delete this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await deletePageRevision({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was deleted successfully!`\n });\n } catch (ex) {\n showWarningToast({\n title: \"Could not delete the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version]);\n\n const publishRevision = useCallback(() => {\n showDialog({\n title: \"Publish revision\",\n icon: <PublishIcon />,\n content: (\n <Text>\n {`You are about to publish revision #${revision.version}. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, publish this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await publishPage({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was published successfully!`\n });\n } catch (ex) {\n showWarningToast({\n title: \"Could not publish the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version]);\n\n const unpublishRevision = useCallback(() => {\n showDialog({\n title: \"Unpublish revision\",\n icon: <UnpublishIcon />,\n content: (\n <Text>\n {`You are about to unpublish revision #${revision.version}, which will take the page off the live website. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, unpublish this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await unpublishPage({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was unpublished successfully!`\n });\n } catch (ex) {\n showWarningToast({\n title: \"Could not unpublish the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version]);\n\n const editRevision = useCallback(() => {\n // The drawer lives outside the editor, so it would stay open across the navigation.\n openRevisionList(false);\n\n goToRoute(Routes.Pages.Editor, {\n id: revision.id\n });\n }, [revision.id, openRevisionList]);\n\n return {\n createRevision,\n editRevision,\n deleteRevision,\n publishRevision,\n unpublishRevision\n };\n};\n"],"names":["useRevision","props","revision","goToRoute","useRouter","openRevisionList","usePageEditorDrawer","showDialog","useDialogs","showSuccessToast","showWarningToast","useToast","createPageRevisionFrom","useCreatePageRevisionFrom","deletePageRevision","useDeletePageRevision","publishPage","usePublishPage","unpublishPage","useUnpublishPage","createRevision","useCallback","deleteRevision","DeleteIcon","Text","ex","publishRevision","PublishIcon","unpublishRevision","UnpublishIcon","editRevision","Routes"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"presentation/pages/PageEditor/Revisions/useRevision.js","sources":["../../../../../src/presentation/pages/PageEditor/Revisions/useRevision.tsx"],"sourcesContent":["import React from \"react\";\nimport type { PageRevision } from \"~/domain/PageRevision/index.js\";\nimport {\n usePublishPage,\n useUnpublishPage,\n useCreatePageRevisionFrom,\n useDeletePageRevision\n} from \"~/features/pages/index.js\";\nimport { useCallback } from \"react\";\nimport { Text, useToast } from \"@webiny/admin-ui\";\nimport { useDialogs } from \"@webiny/app-admin\";\nimport { ReactComponent as PublishIcon } from \"@webiny/icons/visibility.svg\";\nimport { ReactComponent as UnpublishIcon } from \"@webiny/icons/visibility_off.svg\";\nimport { ReactComponent as DeleteIcon } from \"@webiny/icons/delete.svg\";\nimport { Routes } from \"~/routes.js\";\nimport { useRouter } from \"@webiny/app\";\nimport { usePageEditorDrawer } from \"./usePageEditorDrawer.js\";\n\nexport interface UseRevisionProps {\n revision: PageRevision;\n /**\n * Called once the revision is deleted, so the list can move the editor away from a revision\n * that no longer exists.\n */\n onDeleted?: (revision: PageRevision) => void;\n}\n\nexport const useRevision = (props: UseRevisionProps) => {\n const { revision, onDeleted } = props;\n\n const { goToRoute } = useRouter();\n const { openRevisionList } = usePageEditorDrawer();\n const { showDialog } = useDialogs();\n const { showSuccessToast, showWarningToast } = useToast();\n\n const { createPageRevisionFrom } = useCreatePageRevisionFrom();\n const { deletePageRevision } = useDeletePageRevision();\n const { publishPage } = usePublishPage();\n const { unpublishPage } = useUnpublishPage();\n\n const createRevision = useCallback(async () => {\n await createPageRevisionFrom({\n id: revision.id\n });\n }, [revision.id]);\n\n const deleteRevision = useCallback(() => {\n showDialog({\n title: \"Delete revision\",\n icon: <DeleteIcon />,\n content: (\n <Text>\n {`You are about to delete revision #${revision.version}. This cannot be undone. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, delete this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await deletePageRevision({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was deleted successfully!`\n });\n\n onDeleted?.(revision);\n } catch (ex) {\n showWarningToast({\n title: \"Could not delete the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version, onDeleted]);\n\n const publishRevision = useCallback(() => {\n showDialog({\n title: \"Publish revision\",\n icon: <PublishIcon />,\n content: (\n <Text>\n {`You are about to publish revision #${revision.version}. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, publish this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await publishPage({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was published successfully!`\n });\n } catch (ex) {\n showWarningToast({\n title: \"Could not publish the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version]);\n\n const unpublishRevision = useCallback(() => {\n showDialog({\n title: \"Unpublish revision\",\n icon: <UnpublishIcon />,\n content: (\n <Text>\n {`You are about to unpublish revision #${revision.version}, which will take the page off the live website. Are you sure you want to continue?`}\n </Text>\n ),\n acceptLabel: \"Yes, unpublish this revision!\",\n cancelLabel: \"Cancel\",\n onAccept: async () => {\n try {\n await unpublishPage({\n id: revision.id\n });\n\n showSuccessToast({\n title: `Revision #${revision.version} was unpublished successfully!`\n });\n } catch (ex) {\n showWarningToast({\n title: \"Could not unpublish the revision.\",\n description: ex.message\n });\n }\n }\n });\n }, [revision.id, revision.version]);\n\n const editRevision = useCallback(() => {\n // The drawer lives outside the editor, so it would stay open across the navigation.\n openRevisionList(false);\n\n goToRoute(Routes.Pages.Editor, {\n id: revision.id\n });\n }, [revision.id, openRevisionList]);\n\n return {\n createRevision,\n editRevision,\n deleteRevision,\n publishRevision,\n unpublishRevision\n };\n};\n"],"names":["useRevision","props","revision","onDeleted","goToRoute","useRouter","openRevisionList","usePageEditorDrawer","showDialog","useDialogs","showSuccessToast","showWarningToast","useToast","createPageRevisionFrom","useCreatePageRevisionFrom","deletePageRevision","useDeletePageRevision","publishPage","usePublishPage","unpublishPage","useUnpublishPage","createRevision","useCallback","deleteRevision","DeleteIcon","Text","ex","publishRevision","PublishIcon","unpublishRevision","UnpublishIcon","editRevision","Routes"],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ class RevisionListPresenterImpl {
|
|
|
20
20
|
if (!this.entryId) return [];
|
|
21
21
|
const revisions = this.revisionsCache.getItems().filter((revision)=>revision.entryId === this.entryId);
|
|
22
22
|
return orderBy(revisions, [
|
|
23
|
-
"
|
|
23
|
+
"version"
|
|
24
24
|
], [
|
|
25
25
|
"desc"
|
|
26
26
|
]).map((revision)=>this.getItemVm(revision));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/pages/RevisionList/RevisionListPresenter.js","sources":["../../../../src/presentation/pages/RevisionList/RevisionListPresenter.ts"],"sourcesContent":["import { makeAutoObservable } from \"mobx\";\nimport orderBy from \"lodash/orderBy.js\";\nimport { loadingActions, WbPageStatus } from \"~/constants.js\";\nimport { GetPageRevisionsUseCase } from \"~/features/pages/getPageRevisions/abstractions.js\";\nimport {\n PageRevisionsCache,\n WbPageLoadingRepository,\n WbPageRevisionsLoadingRepository\n} from \"~/features/pages/shared/abstractions.js\";\nimport { WbPermissions } from \"~/features/permissions/abstractions.js\";\nimport type { PageRevision } from \"~/domain/PageRevision/PageRevision.js\";\nimport { RevisionListPresenter as PresenterAbstraction } from \"./abstractions.js\";\n\n/**\n * Only actions that run straight from the menu need the overlay. Delete, publish and unpublish\n * are confirmed through a dialog, which shows its own loader while the action runs.\n */\nconst MUTATION_ACTIONS = [loadingActions.createRevisionFrom];\n\nclass RevisionListPresenterImpl implements PresenterAbstraction.Interface {\n private entryId: string | undefined = undefined;\n\n constructor(\n private revisionsCache: PageRevisionsCache.Interface,\n private loadingRepository: WbPageRevisionsLoadingRepository.Interface,\n private pageLoadingRepository: WbPageLoadingRepository.Interface,\n private permissions: WbPermissions.Interface,\n private getPageRevisions: GetPageRevisionsUseCase.Interface\n ) {\n makeAutoObservable(this);\n }\n\n public init(params: PresenterAbstraction.Init) {\n if (this.entryId === params.entryId) {\n return;\n }\n\n this.entryId = params.entryId;\n void this.getPageRevisions.execute({ entryId: params.entryId });\n }\n\n public get vm(): PresenterAbstraction.ViewModel {\n const revisions = this.getRevisions();\n const isLoading = this.getIsLoading();\n\n return {\n revisions,\n isLoading,\n isMutating: this.getIsMutating(),\n isEmpty: !isLoading && revisions.length === 0\n };\n }\n\n private getRevisions = () => {\n if (!this.entryId) {\n return [];\n }\n\n // The cache is shared across pages, so it can hold revisions of previously opened pages.\n const revisions = this.revisionsCache\n .getItems()\n .filter(revision => revision.entryId === this.entryId);\n\n return orderBy(revisions, [\"
|
|
1
|
+
{"version":3,"file":"presentation/pages/RevisionList/RevisionListPresenter.js","sources":["../../../../src/presentation/pages/RevisionList/RevisionListPresenter.ts"],"sourcesContent":["import { makeAutoObservable } from \"mobx\";\nimport orderBy from \"lodash/orderBy.js\";\nimport { loadingActions, WbPageStatus } from \"~/constants.js\";\nimport { GetPageRevisionsUseCase } from \"~/features/pages/getPageRevisions/abstractions.js\";\nimport {\n PageRevisionsCache,\n WbPageLoadingRepository,\n WbPageRevisionsLoadingRepository\n} from \"~/features/pages/shared/abstractions.js\";\nimport { WbPermissions } from \"~/features/permissions/abstractions.js\";\nimport type { PageRevision } from \"~/domain/PageRevision/PageRevision.js\";\nimport { RevisionListPresenter as PresenterAbstraction } from \"./abstractions.js\";\n\n/**\n * Only actions that run straight from the menu need the overlay. Delete, publish and unpublish\n * are confirmed through a dialog, which shows its own loader while the action runs.\n */\nconst MUTATION_ACTIONS = [loadingActions.createRevisionFrom];\n\nclass RevisionListPresenterImpl implements PresenterAbstraction.Interface {\n private entryId: string | undefined = undefined;\n\n constructor(\n private revisionsCache: PageRevisionsCache.Interface,\n private loadingRepository: WbPageRevisionsLoadingRepository.Interface,\n private pageLoadingRepository: WbPageLoadingRepository.Interface,\n private permissions: WbPermissions.Interface,\n private getPageRevisions: GetPageRevisionsUseCase.Interface\n ) {\n makeAutoObservable(this);\n }\n\n public init(params: PresenterAbstraction.Init) {\n if (this.entryId === params.entryId) {\n return;\n }\n\n this.entryId = params.entryId;\n void this.getPageRevisions.execute({ entryId: params.entryId });\n }\n\n public get vm(): PresenterAbstraction.ViewModel {\n const revisions = this.getRevisions();\n const isLoading = this.getIsLoading();\n\n return {\n revisions,\n isLoading,\n isMutating: this.getIsMutating(),\n isEmpty: !isLoading && revisions.length === 0\n };\n }\n\n private getRevisions = () => {\n if (!this.entryId) {\n return [];\n }\n\n // The cache is shared across pages, so it can hold revisions of previously opened pages.\n const revisions = this.revisionsCache\n .getItems()\n .filter(revision => revision.entryId === this.entryId);\n\n // Ordered by version rather than savedOn: publishing or editing an older revision moves its\n // savedOn past the newer ones, which would shuffle the list.\n return orderBy(revisions, [\"version\"], [\"desc\"]).map(revision => this.getItemVm(revision));\n };\n\n private getItemVm = (revision: PageRevision): PresenterAbstraction.ItemViewModel => {\n const canEdit = this.permissions.canEdit(\"page\", revision);\n\n return {\n revision,\n canCreateFrom: canEdit,\n // A locked revision has already been published, so its content can no longer change.\n canEdit: !revision.locked && canEdit,\n canPublish:\n revision.status !== WbPageStatus.Published && this.permissions.canPublish(\"page\"),\n canUnpublish:\n revision.status === WbPageStatus.Published && this.permissions.canUnpublish(\"page\"),\n // Deleting a published or locked revision is allowed, matching the API.\n canDelete: this.permissions.canDelete(\"page\", revision)\n };\n };\n\n private getIsLoading = () => {\n return Boolean(this.loadingRepository.isLoading(loadingActions.get));\n };\n\n /**\n * Watching named actions rather than `hasLoading()`: the `WbPage` namespace is shared with\n * the pages table, so that would also react to listing and fetching.\n */\n private getIsMutating = () => {\n return MUTATION_ACTIONS.some(action => this.pageLoadingRepository.isLoading(action));\n };\n}\n\nexport const RevisionListPresenter = PresenterAbstraction.createImplementation({\n implementation: RevisionListPresenterImpl,\n dependencies: [\n PageRevisionsCache,\n WbPageRevisionsLoadingRepository,\n WbPageLoadingRepository,\n WbPermissions,\n GetPageRevisionsUseCase\n ]\n});\n"],"names":["MUTATION_ACTIONS","loadingActions","RevisionListPresenterImpl","revisionsCache","loadingRepository","pageLoadingRepository","permissions","getPageRevisions","undefined","revisions","revision","orderBy","canEdit","WbPageStatus","Boolean","action","makeAutoObservable","params","isLoading","RevisionListPresenter","PresenterAbstraction","PageRevisionsCache","WbPageRevisionsLoadingRepository","WbPageLoadingRepository","WbPermissions","GetPageRevisionsUseCase"],"mappings":";;;;;;;AAiBA,MAAMA,mBAAmB;IAACC,eAAe,kBAAkB;CAAC;AAE5D,MAAMC;IAGF,YACYC,cAA4C,EAC5CC,iBAA6D,EAC7DC,qBAAwD,EACxDC,WAAoC,EACpCC,gBAAmD,CAC7D;aALUJ,cAAc,GAAdA;aACAC,iBAAiB,GAAjBA;aACAC,qBAAqB,GAArBA;aACAC,WAAW,GAAXA;aACAC,gBAAgB,GAAhBA;aAPJ,OAAO,GAAuBC;aAiC9B,YAAY,GAAG;YACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EACb,OAAO,EAAE;YAIb,MAAMC,YAAY,IAAI,CAAC,cAAc,CAChC,QAAQ,GACR,MAAM,CAACC,CAAAA,WAAYA,SAAS,OAAO,KAAK,IAAI,CAAC,OAAO;YAIzD,OAAOC,QAAQF,WAAW;gBAAC;aAAU,EAAE;gBAAC;aAAO,EAAE,GAAG,CAACC,CAAAA,WAAY,IAAI,CAAC,SAAS,CAACA;QACpF;aAEQ,SAAS,GAAG,CAACA;YACjB,MAAME,UAAU,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQF;YAEjD,OAAO;gBACHA;gBACA,eAAeE;gBAEf,SAAS,CAACF,SAAS,MAAM,IAAIE;gBAC7B,YACIF,SAAS,MAAM,KAAKG,aAAa,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC;gBAC9E,cACIH,SAAS,MAAM,KAAKG,aAAa,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;gBAEhF,WAAW,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,QAAQH;YAClD;QACJ;aAEQ,YAAY,GAAG,IACZI,QAAQ,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAACb,eAAe,GAAG;QAMrE,KACO,aAAa,GAAG,IACbD,iBAAiB,IAAI,CAACe,CAAAA,SAAU,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAACA;QAjE5EC,mBAAmB,IAAI;IAC3B;IAEO,KAAKC,MAAiC,EAAE;QAC3C,IAAI,IAAI,CAAC,OAAO,KAAKA,OAAO,OAAO,EAC/B;QAGJ,IAAI,CAAC,OAAO,GAAGA,OAAO,OAAO;QACxB,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAAE,SAASA,OAAO,OAAO;QAAC;IACjE;IAEA,IAAW,KAAqC;QAC5C,MAAMR,YAAY,IAAI,CAAC,YAAY;QACnC,MAAMS,YAAY,IAAI,CAAC,YAAY;QAEnC,OAAO;YACHT;YACAS;YACA,YAAY,IAAI,CAAC,aAAa;YAC9B,SAAS,CAACA,aAAaT,AAAqB,MAArBA,UAAU,MAAM;QAC3C;IACJ;AA6CJ;AAEO,MAAMU,8CAAwBC,sBAAAA,oBAAyC,CAAC;IAC3E,gBAAgBlB;IAChB,cAAc;QACVmB;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
|
|
@@ -100,6 +100,22 @@ describe("RevisionListPresenter", ()=>{
|
|
|
100
100
|
]);
|
|
101
101
|
expect(presenter.vm.isEmpty).toBeFalse();
|
|
102
102
|
});
|
|
103
|
+
it("should order the revisions by version, even when an older one was saved later", ()=>{
|
|
104
|
+
const presenter = createPresenter();
|
|
105
|
+
revisionsCache.addItems([
|
|
106
|
+
createRevision("page-1#0002", "page-1", 2, "2026-09-21T00:00:00.000Z"),
|
|
107
|
+
createRevision("page-1#0001", "page-1", 1, "2026-09-23T00:00:00.000Z"),
|
|
108
|
+
createRevision("page-1#0003", "page-1", 3, "2026-09-22T00:00:00.000Z")
|
|
109
|
+
]);
|
|
110
|
+
presenter.init({
|
|
111
|
+
entryId: "page-1"
|
|
112
|
+
});
|
|
113
|
+
expect(presenter.vm.revisions.map((item)=>item.revision.id)).toEqual([
|
|
114
|
+
"page-1#0003",
|
|
115
|
+
"page-1#0002",
|
|
116
|
+
"page-1#0001"
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
103
119
|
it("should not report an empty list while loading", ()=>{
|
|
104
120
|
loadingRepository.isLoading.mockReturnValue(true);
|
|
105
121
|
const presenter = createPresenter();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/pages/RevisionList/RevisionListPresenter.test.js","sources":["../../../../src/presentation/pages/RevisionList/RevisionListPresenter.test.ts"],"sourcesContent":["import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { WbPageStatus, type WbStatus } from \"~/constants.js\";\nimport { PageRevision, pageRevisionsCacheFactory } from \"~/domain/PageRevision/index.js\";\nimport { GetPageRevisionsUseCase } from \"~/features/pages/getPageRevisions/abstractions.js\";\nimport {\n PageRevisionsCache,\n WbPageLoadingRepository,\n WbPageRevisionsLoadingRepository\n} from \"~/features/pages/shared/abstractions.js\";\nimport { WbPermissions } from \"~/features/permissions/abstractions.js\";\nimport { RevisionListPresenter as PresenterAbstraction } from \"./abstractions.js\";\nimport { RevisionListPresenter } from \"./RevisionListPresenter.js\";\n\nconst createRevision = (id: string, entryId: string, version: number, savedOn: string) => {\n return PageRevision.create({\n id,\n entryId,\n version,\n savedOn,\n status: WbPageStatus.Draft,\n title: `Page ${entryId}`,\n locked: false,\n createdBy: { id: \"admin\", displayName: \"Admin\", type: \"admin\" },\n createdOn: savedOn,\n revisionDescription: undefined\n });\n};\n\ndescribe(\"RevisionListPresenter\", () => {\n const revisionsCache = pageRevisionsCacheFactory.getCache();\n\n const getPageRevisions = {\n execute: vi.fn().mockResolvedValue([])\n };\n\n const loadingRepository = {\n isLoading: vi.fn().mockReturnValue(false)\n };\n\n const pageLoadingRepository = {\n isLoading: vi.fn().mockReturnValue(false)\n };\n\n const permissions = {\n canEdit: vi.fn().mockReturnValue(true),\n canDelete: vi.fn().mockReturnValue(true),\n canPublish: vi.fn().mockReturnValue(true),\n canUnpublish: vi.fn().mockReturnValue(true)\n };\n\n const createPresenter = () => {\n const container = new Container();\n container.registerInstance(PageRevisionsCache, revisionsCache);\n container.registerInstance(\n WbPageRevisionsLoadingRepository,\n loadingRepository as unknown as WbPageRevisionsLoadingRepository.Interface\n );\n container.registerInstance(\n WbPageLoadingRepository,\n pageLoadingRepository as unknown as WbPageLoadingRepository.Interface\n );\n container.registerInstance(\n WbPermissions,\n permissions as unknown as WbPermissions.Interface\n );\n container.registerInstance(GetPageRevisionsUseCase, getPageRevisions);\n container.register(RevisionListPresenter).inSingletonScope();\n\n return container.resolve(PresenterAbstraction);\n };\n\n beforeEach(() => {\n vi.clearAllMocks();\n loadingRepository.isLoading.mockReturnValue(false);\n pageLoadingRepository.isLoading.mockReturnValue(false);\n permissions.canEdit.mockReturnValue(true);\n permissions.canDelete.mockReturnValue(true);\n permissions.canPublish.mockReturnValue(true);\n permissions.canUnpublish.mockReturnValue(true);\n revisionsCache.clear();\n });\n\n it(\"should fetch the revisions when initialized\", () => {\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n expect(getPageRevisions.execute).toHaveBeenCalledWith({ entryId: \"page-1\" });\n });\n\n it(\"should not refetch the revisions when initialized with the same entry\", () => {\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n presenter.init({ entryId: \"page-1\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n\n presenter.init({ entryId: \"page-2\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(2);\n });\n\n it(\"should only expose the revisions of the current page, newest first\", () => {\n const presenter = createPresenter();\n\n revisionsCache.addItems([\n createRevision(\"page-1#0001\", \"page-1\", 1, \"2026-09-20T00:00:00.000Z\"),\n createRevision(\"page-1#0002\", \"page-1\", 2, \"2026-09-22T00:00:00.000Z\"),\n createRevision(\"page-2#0001\", \"page-2\", 1, \"2026-09-21T00:00:00.000Z\")\n ]);\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions.map(item => item.revision.id)).toEqual([\n \"page-1#0002\",\n \"page-1#0001\"\n ]);\n expect(presenter.vm.isEmpty).toBeFalse();\n });\n\n it(\"should not report an empty list while loading\", () => {\n loadingRepository.isLoading.mockReturnValue(true);\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions).toBeEmpty();\n expect(presenter.vm.isLoading).toBeTrue();\n expect(presenter.vm.isEmpty).toBeFalse();\n });\n\n it(\"should report a mutation in progress\", () => {\n pageLoadingRepository.isLoading.mockImplementation(\n action => action === \"CREATE_REVISION_FROM\"\n );\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeTrue();\n });\n\n it.each([\"PUBLISH\", \"UNPUBLISH\", \"DELETE\"])(\n \"should not report a mutation for %s, which is confirmed through a dialog\",\n action => {\n // Those dialogs show their own loader, so a second one on the drawer is just noise.\n pageLoadingRepository.isLoading.mockImplementation(a => a === action);\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeFalse();\n }\n );\n\n it(\"should not report a mutation for unrelated page actions\", () => {\n // The \"WbPage\" namespace is shared with the pages table.\n pageLoadingRepository.isLoading.mockImplementation(action => action === \"LIST\");\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeFalse();\n });\n\n it(\"should reflect a status change made through the cache\", () => {\n // The revisions menu and the drawer share this presenter, so a mutation made in one of\n // them must be visible in the other without a refetch.\n const presenter = createPresenter();\n\n revisionsCache.addItems([\n createRevision(\"page-1#0001\", \"page-1\", 1, \"2026-09-20T00:00:00.000Z\")\n ]);\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions[0].revision.status).toEqual(WbPageStatus.Draft);\n\n revisionsCache.updateItems(revision =>\n revision.id === \"page-1#0001\" ? revision.withStatus(WbPageStatus.Unpublished) : revision\n );\n\n expect(presenter.vm.revisions[0].revision.status).toEqual(WbPageStatus.Unpublished);\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n });\n\n describe(\"capabilities\", () => {\n const initWith = (status: WbStatus, locked = false) => {\n const revision = PageRevision.create({\n id: \"page-1#0001\",\n entryId: \"page-1\",\n version: 1,\n status,\n locked,\n savedOn: \"2026-09-20T00:00:00.000Z\",\n title: \"Page 1\",\n createdBy: { id: \"admin\", displayName: \"Admin\", type: \"admin\" },\n createdOn: \"2026-09-20T00:00:00.000Z\",\n revisionDescription: undefined\n });\n revisionsCache.addItems([revision]);\n\n const presenter = createPresenter();\n presenter.init({ entryId: \"page-1\" });\n return presenter.vm.revisions[0];\n };\n\n it(\"should allow publishing a draft, but not unpublishing it\", () => {\n const item = initWith(WbPageStatus.Draft);\n\n expect(item.canPublish).toBeTrue();\n expect(item.canUnpublish).toBeFalse();\n });\n\n it(\"should allow unpublishing a published revision, but not publishing it again\", () => {\n const item = initWith(WbPageStatus.Published);\n\n expect(item.canPublish).toBeFalse();\n expect(item.canUnpublish).toBeTrue();\n });\n\n it(\"should not allow editing a locked revision\", () => {\n const item = initWith(WbPageStatus.Unpublished, true);\n\n expect(item.canEdit).toBeFalse();\n // Creating a new revision from a locked one is still fine.\n expect(item.canCreateFrom).toBeTrue();\n });\n\n it(\"should allow deleting a locked revision, matching the API\", () => {\n const item = initWith(WbPageStatus.Published, true);\n\n expect(item.canDelete).toBeTrue();\n });\n\n it(\"should respect the permissions\", () => {\n permissions.canEdit.mockReturnValue(false);\n permissions.canDelete.mockReturnValue(false);\n permissions.canPublish.mockReturnValue(false);\n\n const item = initWith(WbPageStatus.Draft);\n\n expect(item.canCreateFrom).toBeFalse();\n expect(item.canEdit).toBeFalse();\n expect(item.canPublish).toBeFalse();\n expect(item.canDelete).toBeFalse();\n });\n\n it(\"should check delete permission against the revision itself\", () => {\n initWith(WbPageStatus.Draft);\n\n // Ownership is per revision, so the row must be passed to the permission check.\n expect(permissions.canDelete).toHaveBeenCalledWith(\n \"page\",\n expect.objectContaining({ id: \"page-1#0001\" })\n );\n });\n });\n});\n"],"names":["createRevision","id","entryId","version","savedOn","PageRevision","WbPageStatus","undefined","describe","revisionsCache","pageRevisionsCacheFactory","getPageRevisions","vi","loadingRepository","pageLoadingRepository","permissions","createPresenter","container","Container","PageRevisionsCache","WbPageRevisionsLoadingRepository","WbPageLoadingRepository","WbPermissions","GetPageRevisionsUseCase","RevisionListPresenter","PresenterAbstraction","beforeEach","it","presenter","expect","item","action","a","revision","initWith","status","locked"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"presentation/pages/RevisionList/RevisionListPresenter.test.js","sources":["../../../../src/presentation/pages/RevisionList/RevisionListPresenter.test.ts"],"sourcesContent":["import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { WbPageStatus, type WbStatus } from \"~/constants.js\";\nimport { PageRevision, pageRevisionsCacheFactory } from \"~/domain/PageRevision/index.js\";\nimport { GetPageRevisionsUseCase } from \"~/features/pages/getPageRevisions/abstractions.js\";\nimport {\n PageRevisionsCache,\n WbPageLoadingRepository,\n WbPageRevisionsLoadingRepository\n} from \"~/features/pages/shared/abstractions.js\";\nimport { WbPermissions } from \"~/features/permissions/abstractions.js\";\nimport { RevisionListPresenter as PresenterAbstraction } from \"./abstractions.js\";\nimport { RevisionListPresenter } from \"./RevisionListPresenter.js\";\n\nconst createRevision = (id: string, entryId: string, version: number, savedOn: string) => {\n return PageRevision.create({\n id,\n entryId,\n version,\n savedOn,\n status: WbPageStatus.Draft,\n title: `Page ${entryId}`,\n locked: false,\n createdBy: { id: \"admin\", displayName: \"Admin\", type: \"admin\" },\n createdOn: savedOn,\n revisionDescription: undefined\n });\n};\n\ndescribe(\"RevisionListPresenter\", () => {\n const revisionsCache = pageRevisionsCacheFactory.getCache();\n\n const getPageRevisions = {\n execute: vi.fn().mockResolvedValue([])\n };\n\n const loadingRepository = {\n isLoading: vi.fn().mockReturnValue(false)\n };\n\n const pageLoadingRepository = {\n isLoading: vi.fn().mockReturnValue(false)\n };\n\n const permissions = {\n canEdit: vi.fn().mockReturnValue(true),\n canDelete: vi.fn().mockReturnValue(true),\n canPublish: vi.fn().mockReturnValue(true),\n canUnpublish: vi.fn().mockReturnValue(true)\n };\n\n const createPresenter = () => {\n const container = new Container();\n container.registerInstance(PageRevisionsCache, revisionsCache);\n container.registerInstance(\n WbPageRevisionsLoadingRepository,\n loadingRepository as unknown as WbPageRevisionsLoadingRepository.Interface\n );\n container.registerInstance(\n WbPageLoadingRepository,\n pageLoadingRepository as unknown as WbPageLoadingRepository.Interface\n );\n container.registerInstance(\n WbPermissions,\n permissions as unknown as WbPermissions.Interface\n );\n container.registerInstance(GetPageRevisionsUseCase, getPageRevisions);\n container.register(RevisionListPresenter).inSingletonScope();\n\n return container.resolve(PresenterAbstraction);\n };\n\n beforeEach(() => {\n vi.clearAllMocks();\n loadingRepository.isLoading.mockReturnValue(false);\n pageLoadingRepository.isLoading.mockReturnValue(false);\n permissions.canEdit.mockReturnValue(true);\n permissions.canDelete.mockReturnValue(true);\n permissions.canPublish.mockReturnValue(true);\n permissions.canUnpublish.mockReturnValue(true);\n revisionsCache.clear();\n });\n\n it(\"should fetch the revisions when initialized\", () => {\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n expect(getPageRevisions.execute).toHaveBeenCalledWith({ entryId: \"page-1\" });\n });\n\n it(\"should not refetch the revisions when initialized with the same entry\", () => {\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n presenter.init({ entryId: \"page-1\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n\n presenter.init({ entryId: \"page-2\" });\n\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(2);\n });\n\n it(\"should only expose the revisions of the current page, newest first\", () => {\n const presenter = createPresenter();\n\n revisionsCache.addItems([\n createRevision(\"page-1#0001\", \"page-1\", 1, \"2026-09-20T00:00:00.000Z\"),\n createRevision(\"page-1#0002\", \"page-1\", 2, \"2026-09-22T00:00:00.000Z\"),\n createRevision(\"page-2#0001\", \"page-2\", 1, \"2026-09-21T00:00:00.000Z\")\n ]);\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions.map(item => item.revision.id)).toEqual([\n \"page-1#0002\",\n \"page-1#0001\"\n ]);\n expect(presenter.vm.isEmpty).toBeFalse();\n });\n\n it(\"should order the revisions by version, even when an older one was saved later\", () => {\n const presenter = createPresenter();\n\n // Publishing or editing an older revision moves its savedOn past the newer ones.\n revisionsCache.addItems([\n createRevision(\"page-1#0002\", \"page-1\", 2, \"2026-09-21T00:00:00.000Z\"),\n createRevision(\"page-1#0001\", \"page-1\", 1, \"2026-09-23T00:00:00.000Z\"),\n createRevision(\"page-1#0003\", \"page-1\", 3, \"2026-09-22T00:00:00.000Z\")\n ]);\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions.map(item => item.revision.id)).toEqual([\n \"page-1#0003\",\n \"page-1#0002\",\n \"page-1#0001\"\n ]);\n });\n\n it(\"should not report an empty list while loading\", () => {\n loadingRepository.isLoading.mockReturnValue(true);\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions).toBeEmpty();\n expect(presenter.vm.isLoading).toBeTrue();\n expect(presenter.vm.isEmpty).toBeFalse();\n });\n\n it(\"should report a mutation in progress\", () => {\n pageLoadingRepository.isLoading.mockImplementation(\n action => action === \"CREATE_REVISION_FROM\"\n );\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeTrue();\n });\n\n it.each([\"PUBLISH\", \"UNPUBLISH\", \"DELETE\"])(\n \"should not report a mutation for %s, which is confirmed through a dialog\",\n action => {\n // Those dialogs show their own loader, so a second one on the drawer is just noise.\n pageLoadingRepository.isLoading.mockImplementation(a => a === action);\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeFalse();\n }\n );\n\n it(\"should not report a mutation for unrelated page actions\", () => {\n // The \"WbPage\" namespace is shared with the pages table.\n pageLoadingRepository.isLoading.mockImplementation(action => action === \"LIST\");\n const presenter = createPresenter();\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.isMutating).toBeFalse();\n });\n\n it(\"should reflect a status change made through the cache\", () => {\n // The revisions menu and the drawer share this presenter, so a mutation made in one of\n // them must be visible in the other without a refetch.\n const presenter = createPresenter();\n\n revisionsCache.addItems([\n createRevision(\"page-1#0001\", \"page-1\", 1, \"2026-09-20T00:00:00.000Z\")\n ]);\n\n presenter.init({ entryId: \"page-1\" });\n\n expect(presenter.vm.revisions[0].revision.status).toEqual(WbPageStatus.Draft);\n\n revisionsCache.updateItems(revision =>\n revision.id === \"page-1#0001\" ? revision.withStatus(WbPageStatus.Unpublished) : revision\n );\n\n expect(presenter.vm.revisions[0].revision.status).toEqual(WbPageStatus.Unpublished);\n expect(getPageRevisions.execute).toHaveBeenCalledTimes(1);\n });\n\n describe(\"capabilities\", () => {\n const initWith = (status: WbStatus, locked = false) => {\n const revision = PageRevision.create({\n id: \"page-1#0001\",\n entryId: \"page-1\",\n version: 1,\n status,\n locked,\n savedOn: \"2026-09-20T00:00:00.000Z\",\n title: \"Page 1\",\n createdBy: { id: \"admin\", displayName: \"Admin\", type: \"admin\" },\n createdOn: \"2026-09-20T00:00:00.000Z\",\n revisionDescription: undefined\n });\n revisionsCache.addItems([revision]);\n\n const presenter = createPresenter();\n presenter.init({ entryId: \"page-1\" });\n return presenter.vm.revisions[0];\n };\n\n it(\"should allow publishing a draft, but not unpublishing it\", () => {\n const item = initWith(WbPageStatus.Draft);\n\n expect(item.canPublish).toBeTrue();\n expect(item.canUnpublish).toBeFalse();\n });\n\n it(\"should allow unpublishing a published revision, but not publishing it again\", () => {\n const item = initWith(WbPageStatus.Published);\n\n expect(item.canPublish).toBeFalse();\n expect(item.canUnpublish).toBeTrue();\n });\n\n it(\"should not allow editing a locked revision\", () => {\n const item = initWith(WbPageStatus.Unpublished, true);\n\n expect(item.canEdit).toBeFalse();\n // Creating a new revision from a locked one is still fine.\n expect(item.canCreateFrom).toBeTrue();\n });\n\n it(\"should allow deleting a locked revision, matching the API\", () => {\n const item = initWith(WbPageStatus.Published, true);\n\n expect(item.canDelete).toBeTrue();\n });\n\n it(\"should respect the permissions\", () => {\n permissions.canEdit.mockReturnValue(false);\n permissions.canDelete.mockReturnValue(false);\n permissions.canPublish.mockReturnValue(false);\n\n const item = initWith(WbPageStatus.Draft);\n\n expect(item.canCreateFrom).toBeFalse();\n expect(item.canEdit).toBeFalse();\n expect(item.canPublish).toBeFalse();\n expect(item.canDelete).toBeFalse();\n });\n\n it(\"should check delete permission against the revision itself\", () => {\n initWith(WbPageStatus.Draft);\n\n // Ownership is per revision, so the row must be passed to the permission check.\n expect(permissions.canDelete).toHaveBeenCalledWith(\n \"page\",\n expect.objectContaining({ id: \"page-1#0001\" })\n );\n });\n });\n});\n"],"names":["createRevision","id","entryId","version","savedOn","PageRevision","WbPageStatus","undefined","describe","revisionsCache","pageRevisionsCacheFactory","getPageRevisions","vi","loadingRepository","pageLoadingRepository","permissions","createPresenter","container","Container","PageRevisionsCache","WbPageRevisionsLoadingRepository","WbPageLoadingRepository","WbPermissions","GetPageRevisionsUseCase","RevisionListPresenter","PresenterAbstraction","beforeEach","it","presenter","expect","item","action","a","revision","initWith","status","locked"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BaseEditor/defaultConfig/Content/usePreviewDomain.js","sources":["../../../../src/BaseEditor/defaultConfig/Content/usePreviewDomain.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useLocalStorage } from \"@webiny/app\";\nimport { usePreviewDomain as useSharedPreviewDomain } from \"@webiny/frontend-settings/exports/admin.js\";\n\nconst CUSTOM_PREVIEW_DOMAIN = \"custom_preview_domain\";\n\nexport const usePreviewDomain = () => {\n const { previewDomain, isOverridden } = useSharedPreviewDomain();\n const localStorage = useLocalStorage();\n\n const setPreviewDomain = useCallback(\n (domain: string) => {\n if (domain === previewDomain) {\n localStorage.remove(CUSTOM_PREVIEW_DOMAIN);\n } else {\n localStorage.set(CUSTOM_PREVIEW_DOMAIN, domain);\n }\n },\n [previewDomain]\n );\n\n const unsetPreviewDomain = useCallback(() => {\n localStorage.remove(CUSTOM_PREVIEW_DOMAIN);\n }, []);\n\n return {\n previewDomain,\n setPreviewDomain,\n unsetPreviewDomain,\n isOverridden\n };\n};\n"],"names":["CUSTOM_PREVIEW_DOMAIN","usePreviewDomain","previewDomain","isOverridden","useSharedPreviewDomain","localStorage","useLocalStorage","setPreviewDomain","useCallback","domain","unsetPreviewDomain"],"mappings":";;;AAIA,MAAMA,wBAAwB;AAEvB,MAAMC,oCAAmB;IAC5B,MAAM,EAAEC,aAAa,EAAEC,YAAY,EAAE,GAAGC;IACxC,MAAMC,eAAeC;IAErB,MAAMC,mBAAmBC,YACrB,CAACC;QACG,IAAIA,WAAWP,eACXG,aAAa,MAAM,CAACL;aAEpBK,aAAa,GAAG,CAACL,uBAAuBS;IAEhD,GACA;QAACP;KAAc;IAGnB,MAAMQ,qBAAqBF,YAAY;QACnCH,aAAa,MAAM,CAACL;IACxB,GAAG,EAAE;IAEL,OAAO;QACHE;QACAK;QACAG;QACAP;IACJ;AACJ"}
|