@antelopejs/dms 0.7.12 → 0.7.14
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/CHANGELOG.md +36 -0
- package/dist/implementations/dms/client-ip.d.ts +2 -0
- package/dist/implementations/dms/client-ip.js +9 -0
- package/dist/implementations/dms/client-ip.js.map +1 -0
- package/dist/implementations/dms/page.js +14 -1
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -4
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/test/unit/implementations/dms/module-address-page.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/module-address-page.test.js +107 -0
- package/dist/test/unit/implementations/dms/module-address-page.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/client-ip.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/client-ip.test.js +61 -0
- package/dist/test/unit/interfaces/dms/client-ip.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js +30 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/periodLink.ts +60 -0
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +9 -4
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +34 -4
- package/frontend-vue/layers/dms-core/app/types/confirm-dialog.ts +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +22 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/pageValidationRedirect.ts +32 -0
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +27 -56
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +29 -7
- package/frontend-vue/layers/dms-layout/app/types/page.ts +2 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/utils/moduleLanding.ts +56 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +28 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmTextMatch.ts +16 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/numberRange.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +6 -29
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +46 -7
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +23 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/index.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +20 -0
- package/frontend-vue/layers/dms-ui/app/composables/confirm/useConfirmFrom.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +2 -2
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +5 -0
- package/frontend-vue/tests/breadcrumb-target.test.ts +10 -0
- package/frontend-vue/tests/confirm-from.test.ts +101 -0
- package/frontend-vue/tests/confirm-typed-text.test.ts +35 -0
- package/frontend-vue/tests/context-links.test.ts +220 -0
- package/frontend-vue/tests/field-errors.test.ts +35 -0
- package/frontend-vue/tests/form-required-legend.test.ts +34 -0
- package/frontend-vue/tests/form-server-field-errors.test.ts +18 -1
- package/frontend-vue/tests/input-number-range.test.ts +135 -0
- package/frontend-vue/tests/module-routing.test.ts +23 -4
- package/frontend-vue/tests/page-validation-redirect.test.ts +221 -0
- package/frontend-vue/tests/scroll-to-block.test.ts +30 -1
- package/package.json +2 -2
- package/skills/dms-module/SKILL.md +3 -0
- /package/frontend-vue/layers/dms-ui/app/{build/components → components}/form/FormRequiredLegend.vue +0 -0
|
@@ -54,6 +54,8 @@ const permissionsImpl = __importStar(require("../../../../implementations/dms/pe
|
|
|
54
54
|
const realtimeImpl = __importStar(require("../../../../implementations/dms/realtime"));
|
|
55
55
|
const banner_1 = require("@antelopejs/interface-dms/base/banner");
|
|
56
56
|
const chart_1 = require("@antelopejs/interface-dms/base/chart");
|
|
57
|
+
const chart_card_1 = require("@antelopejs/interface-dms/base/chart-card");
|
|
58
|
+
const top_list_card_1 = require("@antelopejs/interface-dms/base/top-list-card");
|
|
57
59
|
const meter_1 = require("@antelopejs/interface-dms/base/meter");
|
|
58
60
|
const stat_group_1 = require("@antelopejs/interface-dms/base/stat-group");
|
|
59
61
|
const tab_1 = require("@antelopejs/interface-dms/base/tab");
|
|
@@ -232,6 +234,34 @@ describe("[unit] interfaces/dms/realtime — page topics", () => {
|
|
|
232
234
|
destroyModule(owner);
|
|
233
235
|
}
|
|
234
236
|
});
|
|
237
|
+
it("registers the topics a chart card or a top list card refetches on", () => {
|
|
238
|
+
const owner = moduleContext("page-topics-cards");
|
|
239
|
+
const meta = (0, interface_core_1.GetMetadata)(SharedPage, page_1.PageMetadata);
|
|
240
|
+
const cards = [
|
|
241
|
+
(0, chart_card_1.ChartCard)({
|
|
242
|
+
title: "Traffic",
|
|
243
|
+
fetchUrl: "/api/traffic",
|
|
244
|
+
realtimeTopic: OWN_TOPIC,
|
|
245
|
+
chart: (0, chart_1.ChartLine)(),
|
|
246
|
+
}),
|
|
247
|
+
(0, top_list_card_1.TopListCard)({
|
|
248
|
+
title: "Slowest",
|
|
249
|
+
fetchUrl: "/api/slowest",
|
|
250
|
+
realtimeTopic: [OTHER_TOPIC],
|
|
251
|
+
}),
|
|
252
|
+
(0, top_list_card_1.TopListCard)({ title: "Quiet", fetchUrl: "/api/quiet" }),
|
|
253
|
+
];
|
|
254
|
+
(0, modules_1.RunWithModuleContext)(owner, () => {
|
|
255
|
+
for (const card of cards)
|
|
256
|
+
card.onPageCreated?.(meta);
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
(0, chai_1.expect)(topicsOf(SharedPage)).to.have.members([OWN_TOPIC, OTHER_TOPIC]);
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
destroyModule(owner);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
235
265
|
it("registers a fetched banner's topics on the page it is placed on", () => {
|
|
236
266
|
const owner = moduleContext("page-topics-banner");
|
|
237
267
|
const banner = (0, banner_1.Banner)({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-topics.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms/page-topics.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"page-topics.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms/page-topics.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** The keys one `PeriodSelector` keeps its period under in the page URL. */
|
|
2
|
+
export interface PeriodQueryKeys {
|
|
3
|
+
period: string;
|
|
4
|
+
compare: string;
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
compareFrom: string;
|
|
8
|
+
compareTo: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const KEY_NAMES: Array<keyof PeriodQueryKeys> = [
|
|
12
|
+
"period",
|
|
13
|
+
"compare",
|
|
14
|
+
"from",
|
|
15
|
+
"to",
|
|
16
|
+
"compareFrom",
|
|
17
|
+
"compareTo",
|
|
18
|
+
];
|
|
19
|
+
const QUERY_SEPARATOR = "?";
|
|
20
|
+
const FRAGMENT_SEPARATOR = "#";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The keys of selector `id` in the page URL (`<id>.period`), or the bare keys
|
|
24
|
+
* (`period`) of a selection kept on its own.
|
|
25
|
+
*/
|
|
26
|
+
export function periodQueryKeys(id?: string): PeriodQueryKeys {
|
|
27
|
+
const prefix = id ? `${id}.` : "";
|
|
28
|
+
return Object.fromEntries(
|
|
29
|
+
KEY_NAMES.map((name) => [name, `${prefix}${name}`]),
|
|
30
|
+
) as unknown as PeriodQueryKeys;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function firstText(value: unknown): string | undefined {
|
|
34
|
+
const text = Array.isArray(value) ? value[0] : value;
|
|
35
|
+
return typeof text === "string" && text ? text : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `link` carrying the period selector `id` shows in `query` (the route query
|
|
40
|
+
* of the page it is followed from), so the page it opens, holding a selector
|
|
41
|
+
* with the same `id`, shows the same period. A key the link names itself
|
|
42
|
+
* wins, and a key the query does not hold is left out.
|
|
43
|
+
*/
|
|
44
|
+
export function withPeriodQuery(
|
|
45
|
+
link: string,
|
|
46
|
+
query: Record<string, unknown>,
|
|
47
|
+
id: string,
|
|
48
|
+
): string {
|
|
49
|
+
const [beforeFragment = "", ...fragment] = link.split(FRAGMENT_SEPARATOR);
|
|
50
|
+
const [path = "", ...search] = beforeFragment.split(QUERY_SEPARATOR);
|
|
51
|
+
const params = new URLSearchParams(search.join(QUERY_SEPARATOR));
|
|
52
|
+
for (const key of Object.values(periodQueryKeys(id))) {
|
|
53
|
+
const value = firstText(query[key]);
|
|
54
|
+
if (value && !params.has(key)) params.set(key, value);
|
|
55
|
+
}
|
|
56
|
+
const text = params.toString();
|
|
57
|
+
return [text ? `${path}${QUERY_SEPARATOR}${text}` : path, ...fragment].join(
|
|
58
|
+
FRAGMENT_SEPARATOR,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -49,15 +49,17 @@ export function resolveI18nKey(
|
|
|
49
49
|
* (`$page.settings.invites.error.not_found`). `translate` echoes the key back
|
|
50
50
|
* when no translation exists in the active locale or its fallbacks: a message
|
|
51
51
|
* marked with `$` then falls back to the bare key, and an unmarked one — which
|
|
52
|
-
* may well be plain prose — is shown as it came in.
|
|
52
|
+
* may well be plain prose — is shown as it came in. `params` fill the
|
|
53
|
+
* translation's placeholders (`"At most {max}"` with `{ max: 5000 }`).
|
|
53
54
|
*/
|
|
54
55
|
export function resolveApiMessage(
|
|
55
56
|
translate: I18nTranslate,
|
|
56
57
|
message: unknown,
|
|
58
|
+
params?: Record<string, unknown> | null,
|
|
57
59
|
): string {
|
|
58
60
|
const text = typeof message === "string" ? message : String(message);
|
|
59
61
|
const marked = readTranslationKey(text);
|
|
60
|
-
const translated = translate(marked.key, {});
|
|
62
|
+
const translated = translate(marked.key, params ?? {});
|
|
61
63
|
|
|
62
64
|
if (translated !== marked.key) return translated;
|
|
63
65
|
return marked.isKey ? marked.key : text;
|
|
@@ -93,8 +95,11 @@ export const useTranslation = () => {
|
|
|
93
95
|
return resolveI18nKey(t, key, params);
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function processApiMessage(
|
|
97
|
-
|
|
98
|
+
function processApiMessage(
|
|
99
|
+
message: unknown,
|
|
100
|
+
params?: Record<string, unknown> | null,
|
|
101
|
+
) {
|
|
102
|
+
return resolveApiMessage(t, message, params);
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
return {
|
|
@@ -22,6 +22,12 @@ export interface ApiFieldError {
|
|
|
22
22
|
message: string;
|
|
23
23
|
/** The values of the field the error names (addresses of a list…). */
|
|
24
24
|
values?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Values filling the placeholders of the translated message: a body
|
|
27
|
+
* `{ field: "budget", message: "$page.error.budget_max", params: { max: 5000 } }`
|
|
28
|
+
* shows "At most 5000" for a translation `"At most {max}"`.
|
|
29
|
+
*/
|
|
30
|
+
params?: ApiFieldErrorParams;
|
|
25
31
|
/**
|
|
26
32
|
* The part of the field the error names, as a dotted path from the field
|
|
27
33
|
* (`address.streetName`, `title.fr`); absent when it names the field.
|
|
@@ -29,6 +35,9 @@ export interface ApiFieldError {
|
|
|
29
35
|
path?: string;
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
/** Placeholder values of a field error's message. */
|
|
39
|
+
export type ApiFieldErrorParams = Record<string, string | number | boolean>;
|
|
40
|
+
|
|
32
41
|
/** A code the API answers with that belongs to a field. */
|
|
33
42
|
export type FieldErrorCode<F extends string = string> =
|
|
34
43
|
| F
|
|
@@ -224,10 +233,26 @@ function fromIssues(issues: ValidationIssue[]): ApiFieldError[] {
|
|
|
224
233
|
});
|
|
225
234
|
}
|
|
226
235
|
|
|
227
|
-
|
|
236
|
+
const PARAM_TYPES = new Set(["string", "number", "boolean"]);
|
|
237
|
+
|
|
238
|
+
/** The placeholder values of a body, keeping only the plain ones. */
|
|
239
|
+
function paramsOf(params: unknown): ApiFieldErrorParams | undefined {
|
|
240
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const entries = Object.entries(params).filter(([, value]) =>
|
|
244
|
+
PARAM_TYPES.has(typeof value),
|
|
245
|
+
);
|
|
246
|
+
return entries.length > 0
|
|
247
|
+
? (Object.fromEntries(entries) as ApiFieldErrorParams)
|
|
248
|
+
: undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** `{ field, message, values?, params? }`, the shape the DMS answers with. */
|
|
228
252
|
function fromFieldObject(body: Record<string, unknown>): ApiFieldError[] {
|
|
229
|
-
const { field, message, values } = body;
|
|
253
|
+
const { field, message, values, params } = body;
|
|
230
254
|
if (typeof field !== "string" || typeof message !== "string") return [];
|
|
255
|
+
const placeholders = paramsOf(params);
|
|
231
256
|
return [
|
|
232
257
|
{
|
|
233
258
|
field,
|
|
@@ -235,6 +260,7 @@ function fromFieldObject(body: Record<string, unknown>): ApiFieldError[] {
|
|
|
235
260
|
values: Array.isArray(values)
|
|
236
261
|
? values.filter((value): value is string => typeof value === "string")
|
|
237
262
|
: undefined,
|
|
263
|
+
...(placeholders && { params: placeholders }),
|
|
238
264
|
},
|
|
239
265
|
];
|
|
240
266
|
}
|
|
@@ -311,7 +337,7 @@ function isClientError(status: number | undefined): status is number {
|
|
|
311
337
|
*
|
|
312
338
|
* Read, in order: a code the form maps to a field (`codes`, for any 4xx), a
|
|
313
339
|
* zod issue list (what `assertValidation` answers), a `{ field, message }`
|
|
314
|
-
* body, the data API's "Missing mandatory fields: a, b" / "Invalid field
|
|
340
|
+
* body (with optional `values` and `params`), the data API's "Missing mandatory fields: a, b" / "Invalid field
|
|
315
341
|
* type(s): a" and a `field: message` text. A network failure, a 403, a 429
|
|
316
342
|
* or a 5xx never resolves to a field.
|
|
317
343
|
*
|
|
@@ -478,7 +504,11 @@ export function useFieldErrors<F extends string>(
|
|
|
478
504
|
if (fields.length === 0) return false;
|
|
479
505
|
clear();
|
|
480
506
|
for (const entry of fields) {
|
|
481
|
-
errors[entry.field as F] = resolveApiMessage(
|
|
507
|
+
errors[entry.field as F] = resolveApiMessage(
|
|
508
|
+
translate,
|
|
509
|
+
entry.message,
|
|
510
|
+
entry.params,
|
|
511
|
+
);
|
|
482
512
|
}
|
|
483
513
|
await focus(fields[0]!.field as F);
|
|
484
514
|
return true;
|
|
@@ -83,6 +83,8 @@ export interface ConfirmDialog {
|
|
|
83
83
|
blocked?: boolean;
|
|
84
84
|
/** Text the user must type to confirm. */
|
|
85
85
|
confirmText?: string;
|
|
86
|
+
/** Compares the typed text with `confirmText` ignoring case. */
|
|
87
|
+
confirmTextCaseInsensitive?: boolean;
|
|
86
88
|
/** Fields whose values are sent with the action's request. */
|
|
87
89
|
fields?: ConfirmDialogField[];
|
|
88
90
|
/** A component drawn in the dialog's body. */
|
|
@@ -5,6 +5,8 @@ import QuickActionsPopover from "./QuickActionsPopover.vue";
|
|
|
5
5
|
import { useSidebarState } from "./sidebarState";
|
|
6
6
|
import { useHeaderActionControls } from "../../composables/layout/useHeaderActionControls";
|
|
7
7
|
import { usePageRecordLabel } from "#dms-core/app/composables/page/usePageRecordLabel";
|
|
8
|
+
import type { CategoryInfo } from "../../../types/page";
|
|
9
|
+
import { resolveModuleLandingSlug } from "../../../utils/moduleLanding";
|
|
8
10
|
|
|
9
11
|
const HOME_ICON = "i-ph-house-light";
|
|
10
12
|
/** Breadcrumb slot of the folded middle crumbs on small screens. */
|
|
@@ -96,6 +98,22 @@ const toggleCurrentPageFavorite = () => {
|
|
|
96
98
|
favoritePages.toggleFavorite(currentPageInfo.value);
|
|
97
99
|
};
|
|
98
100
|
|
|
101
|
+
/** The page a module's root crumb opens: the module's landing page. */
|
|
102
|
+
function moduleLandingSlug(
|
|
103
|
+
metadata: CategoryInfo,
|
|
104
|
+
): string | undefined {
|
|
105
|
+
if (!metadata.isModuleRoot || metadata.hasAccess === false) return undefined;
|
|
106
|
+
const module = siteLayout.modules.value?.[metadata.id];
|
|
107
|
+
if (!module) return undefined;
|
|
108
|
+
return (
|
|
109
|
+
resolveModuleLandingSlug(
|
|
110
|
+
siteLayout.siteLayoutTree.value,
|
|
111
|
+
metadata.id,
|
|
112
|
+
module.landingPage,
|
|
113
|
+
) ?? undefined
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
const breadcrumb = computed((): BreadcrumbItem[] => {
|
|
100
118
|
const base: BreadcrumbItem[] = [
|
|
101
119
|
{ icon: HOME_ICON, to: homepage || "/", "aria-label": t("header.home") },
|
|
@@ -122,7 +140,10 @@ const breadcrumb = computed((): BreadcrumbItem[] => {
|
|
|
122
140
|
|
|
123
141
|
const target = buildBreadcrumbTarget(
|
|
124
142
|
to,
|
|
125
|
-
|
|
143
|
+
{
|
|
144
|
+
...matchingRoute.metadata,
|
|
145
|
+
landingSlug: moduleLandingSlug(matchingRoute.metadata),
|
|
146
|
+
},
|
|
126
147
|
route.query,
|
|
127
148
|
);
|
|
128
149
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseURL, withoutTrailingSlash } from "ufo";
|
|
2
|
+
import {
|
|
3
|
+
type ReplaceUrlVariablesContext,
|
|
4
|
+
resolveLinkVariables,
|
|
5
|
+
} from "#dms-ui/app/build/utils/urlVariables";
|
|
6
|
+
import type { PageValidation } from "../../types/page";
|
|
7
|
+
|
|
8
|
+
const ROOT_PATH = "/";
|
|
9
|
+
|
|
10
|
+
function normalizePath(path: string): string {
|
|
11
|
+
return withoutTrailingSlash(path) || ROOT_PATH;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Where a page that failed its validation sends the user: its `redirectTo`
|
|
16
|
+
* with the page URL's tokens filled, or `undefined` for the not-found error.
|
|
17
|
+
* A target on the page's own path would fail the same way again, so it is
|
|
18
|
+
* refused as well.
|
|
19
|
+
*/
|
|
20
|
+
export function pageValidationRedirect(
|
|
21
|
+
validation: PageValidation | undefined,
|
|
22
|
+
currentPath: string,
|
|
23
|
+
context: ReplaceUrlVariablesContext,
|
|
24
|
+
): string | undefined {
|
|
25
|
+
if (!validation?.redirectTo) return undefined;
|
|
26
|
+
const target = resolveLinkVariables(validation.redirectTo, context);
|
|
27
|
+
const targetPath = parseURL(target).pathname;
|
|
28
|
+
if (!targetPath || normalizePath(targetPath) === normalizePath(currentPath)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { resolveModuleLandingSlug } from "../utils/moduleLanding";
|
|
2
|
+
|
|
1
3
|
const MODULE_NOT_FOUND_KIND = "module-not-found";
|
|
2
4
|
const MODULE_NOT_FOUND_STATUS = 404;
|
|
3
5
|
const MODULE_NOT_FOUND_MESSAGE = "Module not found";
|
|
4
|
-
|
|
6
|
+
|
|
7
|
+
interface GuardedRoute {
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
interface ModuleRouteParts {
|
|
7
12
|
moduleId: string;
|
|
@@ -26,65 +31,26 @@ function parseModuleRoute(path: string): ModuleRouteParts | null {
|
|
|
26
31
|
};
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function findModuleRootNode(
|
|
30
|
-
tree: SiteLayoutTree | undefined,
|
|
31
|
-
moduleId: string,
|
|
32
|
-
): SiteLayoutTree | null {
|
|
33
|
-
if (!tree) return null;
|
|
34
|
-
const modulesNode = tree.children[MODULES_TREE_KEY];
|
|
35
|
-
if (!modulesNode) return null;
|
|
36
|
-
for (const childId of modulesNode.childrenOrders) {
|
|
37
|
-
const child = modulesNode.children[childId];
|
|
38
|
-
if (!child) continue;
|
|
39
|
-
if (child.isModuleRoot && child.id === moduleId) {
|
|
40
|
-
return child;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function isOpenablePage(node: SiteLayoutTree): boolean {
|
|
47
|
-
return !!node.layoutUrl && node.hasAccess !== false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** The first page the caller may open under `node`, in menu order. */
|
|
51
|
-
function findPage(
|
|
52
|
-
node: SiteLayoutTree,
|
|
53
|
-
accepts: (page: SiteLayoutTree) => boolean,
|
|
54
|
-
): SiteLayoutTree | null {
|
|
55
|
-
if (isOpenablePage(node) && accepts(node)) return node;
|
|
56
|
-
for (const childId of node.childrenOrders) {
|
|
57
|
-
const child = node.children[childId];
|
|
58
|
-
if (!child || child.hasAccess === false) continue;
|
|
59
|
-
const found = findPage(child, accepts);
|
|
60
|
-
if (found) return found;
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The page a module opens on: its `landingPage` when the caller may open it,
|
|
67
|
-
* else its first page in menu order.
|
|
68
|
-
*/
|
|
69
|
-
function resolveModuleLandingSlug(
|
|
70
|
-
tree: SiteLayoutTree | undefined,
|
|
71
|
-
moduleId: string,
|
|
72
|
-
landingPage: string | undefined,
|
|
73
|
-
): string | null {
|
|
74
|
-
const moduleRoot = findModuleRootNode(tree, moduleId);
|
|
75
|
-
if (!moduleRoot) return null;
|
|
76
|
-
const declared = landingPage
|
|
77
|
-
? findPage(moduleRoot, (page) => page.id === landingPage)
|
|
78
|
-
: null;
|
|
79
|
-
return (declared ?? findPage(moduleRoot, () => true))?.fullSlug ?? null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
34
|
/** `landing` with the query and hash of `fullPath`, as the module was entered. */
|
|
83
35
|
function withQueryAndHash(landing: string, fullPath: string): string {
|
|
84
36
|
const start = fullPath.search(/[?#]/);
|
|
85
37
|
return start < 0 ? landing : `${landing}${fullPath.slice(start)}`;
|
|
86
38
|
}
|
|
87
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Whether the browser already draws the page at `to`: a link that visits the
|
|
42
|
+
* module's address without a navigation guard gets the landing page served
|
|
43
|
+
* there, and the guard then runs from that page's own setup (`to` is the page
|
|
44
|
+
* on screen). A navigation started from there restarts with each remount of
|
|
45
|
+
* the page it starts, so the browser loads the landing page instead.
|
|
46
|
+
*/
|
|
47
|
+
function isDrawnInBrowser(
|
|
48
|
+
to: GuardedRoute,
|
|
49
|
+
from: GuardedRoute | undefined,
|
|
50
|
+
): boolean {
|
|
51
|
+
return typeof window !== "undefined" && from?.path === to.path;
|
|
52
|
+
}
|
|
53
|
+
|
|
88
54
|
function ensureSiteLayoutLoaded(
|
|
89
55
|
siteLayout: ReturnType<typeof useSiteLayout>,
|
|
90
56
|
): Promise<unknown> | undefined {
|
|
@@ -92,7 +58,7 @@ function ensureSiteLayoutLoaded(
|
|
|
92
58
|
return siteLayout.loadSiteLayout();
|
|
93
59
|
}
|
|
94
60
|
|
|
95
|
-
export default defineDmsMiddleware(async (to) => {
|
|
61
|
+
export default defineDmsMiddleware(async (to, from) => {
|
|
96
62
|
const moduleRoute = parseModuleRoute(to.path);
|
|
97
63
|
if (!moduleRoute) return;
|
|
98
64
|
|
|
@@ -153,7 +119,12 @@ export default defineDmsMiddleware(async (to) => {
|
|
|
153
119
|
// never renders (a 404 before the redirect landed), never enters the
|
|
154
120
|
// history, and a server render answers it with a redirect.
|
|
155
121
|
if (landingSlug && landingSlug !== to.path) {
|
|
156
|
-
|
|
122
|
+
const landing = withQueryAndHash(landingSlug, to.fullPath);
|
|
123
|
+
if (isDrawnInBrowser(to, from)) {
|
|
124
|
+
window.location.replace(landing);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return landing;
|
|
157
128
|
}
|
|
158
129
|
|
|
159
130
|
return redirectToHomepage();
|
|
@@ -3,6 +3,7 @@ import { withoutTrailingSlash } from "ufo";
|
|
|
3
3
|
import { holdPageHeaderActions } from "../composables/layout/usePageHeaderActions";
|
|
4
4
|
import { useDevReloading } from "../build/composables/dev-reload/useDevReloadHolder";
|
|
5
5
|
import { usePageLiveHost } from "#dms-ui/app/build/composables/live/usePageLiveHost";
|
|
6
|
+
import { pageValidationRedirect } from "../build/utils/pageValidationRedirect";
|
|
6
7
|
|
|
7
8
|
defineOptions({ inheritAttrs: false });
|
|
8
9
|
|
|
@@ -95,13 +96,13 @@ async function recheckAccessWithFreshSession(): Promise<void> {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
function
|
|
99
|
+
function isPageValid(
|
|
99
100
|
metadata: PageInfo | undefined,
|
|
100
101
|
query: Record<string, unknown>,
|
|
101
102
|
params: Record<string, unknown>,
|
|
102
|
-
):
|
|
103
|
+
): boolean {
|
|
103
104
|
if (!metadata?.validation) {
|
|
104
|
-
return;
|
|
105
|
+
return true;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
const { validation } = metadata;
|
|
@@ -121,12 +122,31 @@ function validatePage(
|
|
|
121
122
|
{ query, params },
|
|
122
123
|
);
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
return isQueryValid && isCustomValid;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Set while a page failing its validation leaves for its `redirectTo`: its
|
|
129
|
+
// blocks are not drawn, so none of them requests a URL missing its context.
|
|
130
|
+
const isLeavingPage = ref(false);
|
|
131
|
+
|
|
132
|
+
async function enforcePageValidation(): Promise<void> {
|
|
133
|
+
const metadata = pagelayoutMetadata.value;
|
|
134
|
+
const query = route.query as Record<string, unknown>;
|
|
135
|
+
if (isPageValid(metadata, query, routeParams.value)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const redirect = pageValidationRedirect(metadata?.validation, route.path, {
|
|
139
|
+
routeParams: routeParams.value,
|
|
140
|
+
routeQuery: query,
|
|
141
|
+
});
|
|
142
|
+
if (!redirect) {
|
|
125
143
|
throw createError({
|
|
126
144
|
statusCode: HTTP_NOT_FOUND,
|
|
127
145
|
statusMessage: "Page not found",
|
|
128
146
|
});
|
|
129
147
|
}
|
|
148
|
+
isLeavingPage.value = true;
|
|
149
|
+
await navigateDms(redirect, { replace: true });
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
defineDmsPageMeta({
|
|
@@ -159,7 +179,7 @@ if (siteLayout.loadingError && siteLayout.loadingError.value) {
|
|
|
159
179
|
});
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
|
|
182
|
+
await enforcePageValidation();
|
|
163
183
|
await checkPageMetadata(pagelayoutMetadata.value);
|
|
164
184
|
|
|
165
185
|
// Same reason as the guard inside `checkPageMetadata`: while a dev reload is in
|
|
@@ -207,7 +227,8 @@ watch(
|
|
|
207
227
|
siteLayout.refreshIfStale(),
|
|
208
228
|
refreshPermissionsIfStale(),
|
|
209
229
|
]);
|
|
210
|
-
|
|
230
|
+
isLeavingPage.value = false;
|
|
231
|
+
await enforcePageValidation();
|
|
211
232
|
await checkPageMetadata(pagelayoutMetadata.value);
|
|
212
233
|
},
|
|
213
234
|
);
|
|
@@ -488,7 +509,8 @@ if (import.meta.env.DEV) {
|
|
|
488
509
|
</script>
|
|
489
510
|
|
|
490
511
|
<template>
|
|
491
|
-
<
|
|
512
|
+
<template v-if="isLeavingPage" />
|
|
513
|
+
<div v-else-if="hidesEveryBlock" class="dms-card overflow-hidden">
|
|
492
514
|
<DmsEmptyState
|
|
493
515
|
variant="no-access"
|
|
494
516
|
:title="t('page.no_visible_blocks.title')"
|
|
@@ -9,6 +9,8 @@ export type MenuItemType = "label" | "link";
|
|
|
9
9
|
export interface PageValidation {
|
|
10
10
|
requiredQueryParams?: string[];
|
|
11
11
|
customFunctionId?: string;
|
|
12
|
+
/** Where a page failing its validation sends the user, not-found aside. */
|
|
13
|
+
redirectTo?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface MenuOptions {
|
|
@@ -3,6 +3,8 @@ import type { PageValidation } from "../types/page";
|
|
|
3
3
|
export interface BreadcrumbTargetSource {
|
|
4
4
|
layoutUrl?: string;
|
|
5
5
|
validation?: PageValidation;
|
|
6
|
+
/** A module's root: the page the module opens on, its crumb's link. */
|
|
7
|
+
landingSlug?: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -32,8 +34,9 @@ export function buildPageTarget(
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* The link of a breadcrumb crumb: the page's {@link buildPageTarget}.
|
|
36
|
-
*
|
|
37
|
+
* The link of a breadcrumb crumb: the page's {@link buildPageTarget}. A
|
|
38
|
+
* module's root links to its landing page; other categories without a page
|
|
39
|
+
* behind them get no link.
|
|
37
40
|
*/
|
|
38
41
|
export function buildBreadcrumbTarget(
|
|
39
42
|
path: string,
|
|
@@ -41,7 +44,7 @@ export function buildBreadcrumbTarget(
|
|
|
41
44
|
currentQuery: Record<string, unknown>,
|
|
42
45
|
): string | undefined {
|
|
43
46
|
if (!metadata.layoutUrl) {
|
|
44
|
-
return
|
|
47
|
+
return metadata.landingSlug;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
return buildPageTarget(path, metadata.validation, currentQuery);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SiteLayoutTree } from "../types/page";
|
|
2
|
+
|
|
3
|
+
const MODULES_TREE_KEY = "modules";
|
|
4
|
+
|
|
5
|
+
function findModuleRootNode(
|
|
6
|
+
tree: SiteLayoutTree | undefined,
|
|
7
|
+
moduleId: string,
|
|
8
|
+
): SiteLayoutTree | null {
|
|
9
|
+
if (!tree) return null;
|
|
10
|
+
const modulesNode = tree.children[MODULES_TREE_KEY];
|
|
11
|
+
if (!modulesNode) return null;
|
|
12
|
+
for (const childId of modulesNode.childrenOrders) {
|
|
13
|
+
const child = modulesNode.children[childId];
|
|
14
|
+
if (!child) continue;
|
|
15
|
+
if (child.isModuleRoot && child.id === moduleId) {
|
|
16
|
+
return child;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isOpenablePage(node: SiteLayoutTree): boolean {
|
|
23
|
+
return !!node.layoutUrl && node.hasAccess !== false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The first page the caller may open under `node`, in menu order. */
|
|
27
|
+
function findPage(
|
|
28
|
+
node: SiteLayoutTree,
|
|
29
|
+
accepts: (page: SiteLayoutTree) => boolean,
|
|
30
|
+
): SiteLayoutTree | null {
|
|
31
|
+
if (isOpenablePage(node) && accepts(node)) return node;
|
|
32
|
+
for (const childId of node.childrenOrders) {
|
|
33
|
+
const child = node.children[childId];
|
|
34
|
+
if (!child || child.hasAccess === false) continue;
|
|
35
|
+
const found = findPage(child, accepts);
|
|
36
|
+
if (found) return found;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The page a module opens on: its `landingPage` when the caller may open it,
|
|
43
|
+
* else its first page in menu order.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveModuleLandingSlug(
|
|
46
|
+
tree: SiteLayoutTree | undefined,
|
|
47
|
+
moduleId: string,
|
|
48
|
+
landingPage: string | undefined,
|
|
49
|
+
): string | null {
|
|
50
|
+
const moduleRoot = findModuleRootNode(tree, moduleId);
|
|
51
|
+
if (!moduleRoot) return null;
|
|
52
|
+
const declared = landingPage
|
|
53
|
+
? findPage(moduleRoot, (page) => page.id === landingPage)
|
|
54
|
+
: null;
|
|
55
|
+
return (declared ?? findPage(moduleRoot, () => true))?.fullSlug ?? null;
|
|
56
|
+
}
|