@antelopejs/dms 0.7.21 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -3
- package/dist/implementations/dms/resolver-timeout.js.map +1 -1
- package/dist/implementations/dms-auth/account-emails.d.ts +4 -0
- package/dist/implementations/dms-auth/account-emails.js +62 -0
- package/dist/implementations/dms-auth/account-emails.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +1 -3
- package/dist/implementations/dms-auth/index.js +15 -61
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/pages/settings/users/email-change.js +3 -4
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invites.js +1 -1
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.js +1 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -0
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/test/antelope.test.js +2 -1
- package/dist/test/antelope.test.js.map +1 -1
- package/dist/test/helpers/db.js +19 -11
- package/dist/test/helpers/db.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +4 -2
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js +8 -5
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.js +20 -7
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js +34 -0
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js +49 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js +17 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +2 -2
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js +5 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +12 -2
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +29 -4
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +14 -11
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js +8 -5
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js.map +1 -1
- package/dist/test/unit/utils/email-subjects.test.js +27 -0
- package/dist/test/unit/utils/email-subjects.test.js.map +1 -0
- package/dist/utils/admin-invite-email.js +2 -4
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-change-mail.d.ts +1 -1
- package/dist/utils/email-change-mail.js +11 -6
- package/dist/utils/email-change-mail.js.map +1 -1
- package/dist/utils/email-subjects.d.ts +3 -0
- package/dist/utils/email-subjects.js +32 -0
- package/dist/utils/email-subjects.js.map +1 -0
- package/dist/utils/export-jobs.js +15 -10
- package/dist/utils/export-jobs.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +29 -11
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +17 -0
- package/frontend-vue/layers/dms-layout/app/app.config.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +18 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +16 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +14 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/HStack.vue +16 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/VStack.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/composables/useFooterLinks.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeNotice.vue +13 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeVerification.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailExportReady.vue +23 -12
- package/frontend-vue/layers/dms-layout/app/emails/EmailResetPassword.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailTwoFactor.vue +23 -4
- package/frontend-vue/layers/dms-layout/app/emails/EmailUserValidation.vue +18 -6
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailOTP.vue +8 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +40 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +40 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +11 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +27 -36
- package/frontend-vue/layers/dms-ui/app/build/composables/chart/apexTheme.ts +59 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +0 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryOrder.ts +23 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +19 -15
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/page/pageUrlValues.ts +41 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +2 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +7 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/link.ts +7 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +32 -9
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +9 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +5 -11
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +32 -1
- package/frontend-vue/layers/dms-ui/app/components/check-list/CheckList.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +7 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +14 -6
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +52 -3
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +53 -31
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexPlotOptions.ts +15 -4
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexYRange.ts +123 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +49 -49
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartTheme.ts +20 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/form/types/props.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +8 -6
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/utils/bannerToneIcons.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +26 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -0
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +7 -0
- package/frontend-vue/tests/banner-tone-icon.test.ts +55 -0
- package/frontend-vue/tests/builtin-emails.test.ts +141 -0
- package/frontend-vue/tests/cascader.test.ts +27 -0
- package/frontend-vue/tests/chart-auto-y-range.test.ts +25 -9
- package/frontend-vue/tests/chart-colors.test.ts +8 -0
- package/frontend-vue/tests/chart-resize-after-animation.test.ts +100 -0
- package/frontend-vue/tests/chart-type-defaults.test.ts +128 -0
- package/frontend-vue/tests/check-list-marker.test.ts +58 -0
- package/frontend-vue/tests/confirm-from.test.ts +1 -1
- package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -2
- package/frontend-vue/tests/context-links.test.ts +2 -2
- package/frontend-vue/tests/custom-action-confirm-from.test.ts +2 -2
- package/frontend-vue/tests/download-action-target.test.ts +2 -2
- package/frontend-vue/tests/file-drop-prompt.test.ts +72 -0
- package/frontend-vue/tests/footer-config.test.ts +135 -0
- package/frontend-vue/tests/form-actions.test.ts +7 -0
- package/frontend-vue/tests/form-enclosing-form.test.ts +0 -21
- package/frontend-vue/tests/form-entry-order.test.ts +45 -0
- package/frontend-vue/tests/form-footer-kind.test.ts +11 -6
- package/frontend-vue/tests/form-load-error.test.ts +38 -23
- package/frontend-vue/tests/grid-responsive-columns.test.ts +39 -31
- package/frontend-vue/tests/header-actions.test.ts +55 -9
- package/frontend-vue/tests/instant-save-badge.test.ts +11 -4
- package/frontend-vue/tests/list-row-truncate.test.ts +46 -0
- package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +1 -28
- package/frontend-vue/tests/period-live-pause.test.ts +0 -13
- package/frontend-vue/tests/period-max-days.test.ts +0 -14
- package/frontend-vue/tests/roles-name-errors.test.ts +2 -3
- package/frontend-vue/tests/row-side-panel.test.ts +0 -12
- package/frontend-vue/tests/save-bar.test.ts +15 -3
- package/frontend-vue/tests/save-status-error.test.ts +2 -2
- package/frontend-vue/tests/table-cell-wrap.test.ts +0 -12
- package/frontend-vue/tests/table-density-menu.test.ts +0 -21
- package/frontend-vue/tests/table-live-hold.test.ts +0 -44
- package/frontend-vue/tests/table-row-action-errors.test.ts +5 -5
- package/frontend-vue/tests/table-row-hover.test.ts +2 -12
- package/frontend-vue/tests/table-scroll-area.test.ts +0 -8
- package/frontend-vue/tests/table-view-card-props.test.ts +0 -17
- package/frontend-vue/tests/table-view-cards-reorder.test.ts +0 -16
- package/frontend-vue/tests/table-view-grouped.test.ts +0 -55
- package/frontend-vue/tests/table-view-pinned-rows.test.ts +0 -15
- package/frontend-vue/tests/table-view-record-link.test.ts +0 -17
- package/frontend-vue/tests/table-view-refresh.test.ts +0 -17
- package/frontend-vue/tests/table-view-sources.test.ts +1 -1
- package/frontend-vue/tests/table-view-url-tab.test.ts +0 -14
- package/frontend-vue/tests/template-tokens.test.ts +235 -0
- package/frontend-vue/tests/tone.test.ts +0 -19
- package/frontend-vue/tests/url-interpolation.test.ts +6 -6
- package/package.json +6 -6
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-pages/REFERENCE.md +2 -2
- package/skills/dms-project/SKILL.md +0 -1
- package/dist/test/integration/auth/login.test.js +0 -57
- package/dist/test/integration/auth/login.test.js.map +0 -1
- package/dist/test/integration/auth/refresh.test.js +0 -59
- package/dist/test/integration/auth/refresh.test.js.map +0 -1
- package/dist/test/integration/auth/sessions.test.js +0 -58
- package/dist/test/integration/auth/sessions.test.js.map +0 -1
- package/dist/test/integration/auth/signup.test.js +0 -99
- package/dist/test/integration/auth/signup.test.js.map +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.d.ts +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.js +0 -137
- package/dist/test/unit/skills/dms-dev-script.test.js.map +0 -1
- package/frontend-vue/tests/form-reload-on-reopen.test.ts +0 -20
- package/frontend-vue/tests/icon-aria-hidden.test.ts +0 -29
- package/frontend-vue/tests/skeleton-paragraph.test.ts +0 -30
- package/frontend-vue/tests/tab-slot-spacing.test.ts +0 -29
- package/frontend-vue/tests/table-empty-body-viewport.test.ts +0 -25
- package/frontend-vue/tests/table-header-placement.test.ts +0 -21
- package/frontend-vue/tests/vue-inventory.test.ts +0 -107
- /package/dist/test/{integration/auth/login.test.d.ts → unit/implementations/dms-html-render/built-in-emails.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/sessions.test.d.ts → unit/interfaces/dms/hook-handler-results.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/refresh.test.d.ts → unit/interfaces/dms-base/query-response-delta.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/signup.test.d.ts → unit/utils/email-subjects.test.d.ts} +0 -0
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "vue-i18n";
|
|
2
3
|
import EmailLayout from "./components/EmailLayout.vue";
|
|
3
4
|
import EmailOTP from "./components/EmailOTP.vue";
|
|
5
|
+
import { emailStyles } from "./components/index";
|
|
4
6
|
|
|
5
7
|
defineProps<{
|
|
6
8
|
userName: string;
|
|
7
9
|
verificationCode: string;
|
|
8
10
|
}>();
|
|
11
|
+
|
|
12
|
+
const { t, locale } = useI18n();
|
|
9
13
|
</script>
|
|
10
14
|
|
|
11
15
|
<template>
|
|
12
|
-
<EmailLayout
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
<EmailLayout
|
|
17
|
+
:title="t('emails.two_factor.title')"
|
|
18
|
+
:lang="locale"
|
|
19
|
+
public-logo-only
|
|
20
|
+
>
|
|
21
|
+
<EText :style="emailStyles.text">
|
|
22
|
+
{{ t("emails.common.greeting", { name: userName }) }}
|
|
23
|
+
</EText>
|
|
24
|
+
|
|
25
|
+
<EText :style="emailStyles.text">
|
|
26
|
+
{{ t("emails.two_factor.body") }}
|
|
27
|
+
</EText>
|
|
28
|
+
|
|
15
29
|
<EmailOTP :code="verificationCode" />
|
|
16
|
-
|
|
30
|
+
|
|
31
|
+
<template #footer>
|
|
32
|
+
<EText :style="emailStyles.footer">
|
|
33
|
+
{{ t("emails.two_factor.footer") }}
|
|
34
|
+
</EText>
|
|
35
|
+
</template>
|
|
17
36
|
</EmailLayout>
|
|
18
37
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "vue-i18n";
|
|
2
3
|
import EmailLayout from "./components/EmailLayout.vue";
|
|
3
4
|
import EmailOTP from "./components/EmailOTP.vue";
|
|
4
5
|
import { emailStyles } from "./components/index";
|
|
@@ -7,23 +8,34 @@ defineProps<{
|
|
|
7
8
|
userName: string;
|
|
8
9
|
validationCode: string;
|
|
9
10
|
expiresIn: string;
|
|
11
|
+
expiresInHours: number;
|
|
10
12
|
}>();
|
|
13
|
+
|
|
14
|
+
const { t, locale } = useI18n();
|
|
11
15
|
</script>
|
|
12
16
|
|
|
13
17
|
<template>
|
|
14
|
-
<EmailLayout
|
|
15
|
-
|
|
18
|
+
<EmailLayout
|
|
19
|
+
:title="t('emails.user_validation.title')"
|
|
20
|
+
:lang="locale"
|
|
21
|
+
public-logo-only
|
|
22
|
+
>
|
|
23
|
+
<EText :style="emailStyles.text">
|
|
24
|
+
{{ t("emails.common.greeting", { name: userName }) }}
|
|
25
|
+
</EText>
|
|
16
26
|
|
|
17
27
|
<EText :style="emailStyles.text">
|
|
18
|
-
|
|
19
|
-
email address:
|
|
28
|
+
{{ t("emails.user_validation.body") }}
|
|
20
29
|
</EText>
|
|
21
30
|
|
|
22
|
-
<EmailOTP
|
|
31
|
+
<EmailOTP
|
|
32
|
+
:code="validationCode"
|
|
33
|
+
:expires-in="t('emails.common.hours', expiresInHours)"
|
|
34
|
+
/>
|
|
23
35
|
|
|
24
36
|
<template #footer>
|
|
25
37
|
<EText :style="emailStyles.footer">
|
|
26
|
-
|
|
38
|
+
{{ t("emails.user_validation.footer") }}
|
|
27
39
|
</EText>
|
|
28
40
|
</template>
|
|
29
41
|
</EmailLayout>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "vue-i18n";
|
|
2
3
|
import { emailStyles } from "./index";
|
|
3
4
|
|
|
4
5
|
defineProps<{
|
|
@@ -6,17 +7,22 @@ defineProps<{
|
|
|
6
7
|
expiresIn?: string;
|
|
7
8
|
expiresLabel?: string;
|
|
8
9
|
}>();
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n();
|
|
9
12
|
</script>
|
|
10
13
|
|
|
11
14
|
<template>
|
|
12
15
|
<ESection :style="emailStyles.otp.box">
|
|
13
|
-
<EText :style="emailStyles.otp.label">
|
|
16
|
+
<EText :style="emailStyles.otp.label">
|
|
17
|
+
{{ t("emails.common.code_label") }}
|
|
18
|
+
</EText>
|
|
14
19
|
<EText :style="emailStyles.otp.code">
|
|
15
20
|
{{ code }}
|
|
16
21
|
</EText>
|
|
17
22
|
</ESection>
|
|
18
23
|
|
|
19
24
|
<EText v-if="expiresIn" :style="emailStyles.expires">
|
|
20
|
-
{{ expiresLabel ?? "
|
|
25
|
+
{{ expiresLabel ?? t("emails.common.code_expires_label") }}
|
|
26
|
+
{{ expiresIn }}.
|
|
21
27
|
</EText>
|
|
22
28
|
</template>
|
|
@@ -867,6 +867,46 @@
|
|
|
867
867
|
"expires_label": "This invitation expires in",
|
|
868
868
|
"expires_in": "{days} days",
|
|
869
869
|
"footer": "If you didn't expect this invitation, you can safely ignore this email."
|
|
870
|
+
},
|
|
871
|
+
"common": {
|
|
872
|
+
"greeting": "Hello {name},",
|
|
873
|
+
"code_label": "Your verification code is:",
|
|
874
|
+
"code_expires_label": "This code expires in",
|
|
875
|
+
"hours": "{count} hour | {count} hours"
|
|
876
|
+
},
|
|
877
|
+
"user_validation": {
|
|
878
|
+
"title": "Verify Your Email",
|
|
879
|
+
"body": "Thank you for signing up! Enter the verification code below to verify your email address:",
|
|
880
|
+
"footer": "If you didn't create an account, you can safely ignore this email."
|
|
881
|
+
},
|
|
882
|
+
"reset_password": {
|
|
883
|
+
"title": "Reset Your Password",
|
|
884
|
+
"body": "We received a request to reset your password. Enter the verification code below to reset your password:",
|
|
885
|
+
"footer": "If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged."
|
|
886
|
+
},
|
|
887
|
+
"two_factor": {
|
|
888
|
+
"title": "Your verification code",
|
|
889
|
+
"body": "Use this verification code to finish signing in:",
|
|
890
|
+
"footer": "If you did not attempt to sign in, you can safely ignore this email."
|
|
891
|
+
},
|
|
892
|
+
"email_change_verification": {
|
|
893
|
+
"title": "Confirm your new sign-in email",
|
|
894
|
+
"body": "Enter the code below to make {email} the sign-in email of your account:",
|
|
895
|
+
"footer": "If you didn't ask for this change, you can safely ignore this email: your account keeps its current address."
|
|
896
|
+
},
|
|
897
|
+
"email_change_notice": {
|
|
898
|
+
"title": "Your sign-in email is being changed",
|
|
899
|
+
"body": "Someone signed in to your account asked to change its sign-in email to {email}. The change takes effect once that address is confirmed.",
|
|
900
|
+
"footer": "If this wasn't you, change your password now and sign out the other sessions in Security: your account keeps this address until the new one is confirmed."
|
|
901
|
+
},
|
|
902
|
+
"export_ready": {
|
|
903
|
+
"title": "Your data export is ready",
|
|
904
|
+
"body": "Your data export has been prepared. Use the button below to download the archive:",
|
|
905
|
+
"button": "Download export",
|
|
906
|
+
"expires_label": "This download link expires in",
|
|
907
|
+
"partial": "This export is {incomplete}: the following parts could not be included — {sources}. Request a new export if you need them.",
|
|
908
|
+
"incomplete": "incomplete",
|
|
909
|
+
"footer": "If you didn't request this export, you can safely ignore this email."
|
|
870
910
|
}
|
|
871
911
|
},
|
|
872
912
|
"empty_layout": {
|
|
@@ -871,6 +871,46 @@
|
|
|
871
871
|
"expires_label": "Cette invitation expire dans",
|
|
872
872
|
"expires_in": "{days} jours",
|
|
873
873
|
"footer": "Si vous n'attendiez pas cette invitation, vous pouvez ignorer cet e-mail."
|
|
874
|
+
},
|
|
875
|
+
"common": {
|
|
876
|
+
"greeting": "Bonjour {name},",
|
|
877
|
+
"code_label": "Votre code de vérification :",
|
|
878
|
+
"code_expires_label": "Ce code expire dans",
|
|
879
|
+
"hours": "{count} heure | {count} heures"
|
|
880
|
+
},
|
|
881
|
+
"user_validation": {
|
|
882
|
+
"title": "Vérifiez votre adresse e-mail",
|
|
883
|
+
"body": "Merci pour votre inscription ! Saisissez le code ci-dessous pour vérifier votre adresse e-mail :",
|
|
884
|
+
"footer": "Si vous n'avez pas créé de compte, vous pouvez ignorer cet e-mail."
|
|
885
|
+
},
|
|
886
|
+
"reset_password": {
|
|
887
|
+
"title": "Réinitialisez votre mot de passe",
|
|
888
|
+
"body": "Nous avons reçu une demande de réinitialisation de votre mot de passe. Saisissez le code ci-dessous pour le réinitialiser :",
|
|
889
|
+
"footer": "Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail. Votre mot de passe reste inchangé."
|
|
890
|
+
},
|
|
891
|
+
"two_factor": {
|
|
892
|
+
"title": "Votre code de vérification",
|
|
893
|
+
"body": "Saisissez ce code de vérification pour terminer votre connexion :",
|
|
894
|
+
"footer": "Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail."
|
|
895
|
+
},
|
|
896
|
+
"email_change_verification": {
|
|
897
|
+
"title": "Confirmez votre nouvelle adresse de connexion",
|
|
898
|
+
"body": "Saisissez le code ci-dessous pour faire de {email} l'adresse de connexion de votre compte :",
|
|
899
|
+
"footer": "Si vous n'avez pas demandé ce changement, vous pouvez ignorer cet e-mail : votre compte garde son adresse actuelle."
|
|
900
|
+
},
|
|
901
|
+
"email_change_notice": {
|
|
902
|
+
"title": "Votre adresse de connexion va changer",
|
|
903
|
+
"body": "Une personne connectée à votre compte a demandé à remplacer son adresse de connexion par {email}. Le changement prendra effet une fois cette adresse confirmée.",
|
|
904
|
+
"footer": "Si ce n'est pas vous, changez votre mot de passe maintenant et déconnectez les autres sessions dans Sécurité : votre compte garde cette adresse tant que la nouvelle n'est pas confirmée."
|
|
905
|
+
},
|
|
906
|
+
"export_ready": {
|
|
907
|
+
"title": "Votre export de données est prêt",
|
|
908
|
+
"body": "Votre export de données a été préparé. Utilisez le bouton ci-dessous pour télécharger l'archive :",
|
|
909
|
+
"button": "Télécharger l'export",
|
|
910
|
+
"expires_label": "Ce lien de téléchargement expire dans",
|
|
911
|
+
"partial": "Cet export est {incomplete} : les parties suivantes n'ont pas pu être incluses — {sources}. Demandez un nouvel export si vous en avez besoin.",
|
|
912
|
+
"incomplete": "incomplet",
|
|
913
|
+
"footer": "Si vous n'avez pas demandé cet export, vous pouvez ignorer cet e-mail."
|
|
874
914
|
}
|
|
875
915
|
},
|
|
876
916
|
"empty_layout": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { pageLink } from "../../composables/page/pageUrlValues";
|
|
2
3
|
import type {
|
|
3
4
|
FormField,
|
|
4
5
|
FormFieldReadonly,
|
|
@@ -79,7 +80,7 @@ const readonly = computed<FormFieldReadonly>(() =>
|
|
|
79
80
|
/>
|
|
80
81
|
<DmsAutoLink
|
|
81
82
|
v-if="readonly.link"
|
|
82
|
-
:to="readonly.link.to"
|
|
83
|
+
:to="pageLink(readonly.link.to)"
|
|
83
84
|
class="text-primary ms-auto text-[12.5px] font-medium"
|
|
84
85
|
>
|
|
85
86
|
{{ processI18n(readonly.link.label) }}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import DmsEmptyState
|
|
2
|
+
import DmsEmptyState, {
|
|
3
|
+
type EmptyStateVariant,
|
|
4
|
+
} from "../../../components/empty-state/EmptyState.vue";
|
|
3
5
|
import type { FormLoadFailure } from "../../composables/form/formRecordLoad";
|
|
4
6
|
|
|
5
7
|
// What a form shows instead of its fields when the record it edits did not
|
|
@@ -21,16 +23,22 @@ const props = withDefaults(defineProps<FormLoadErrorProps>(), {
|
|
|
21
23
|
const emit = defineEmits<FormLoadErrorEmits>();
|
|
22
24
|
|
|
23
25
|
const { t } = useI18n();
|
|
26
|
+
|
|
27
|
+
const VARIANTS: Record<FormLoadFailure, EmptyStateVariant> = {
|
|
28
|
+
forbidden: "no-access",
|
|
29
|
+
missing: "no-data",
|
|
30
|
+
error: "error",
|
|
31
|
+
};
|
|
24
32
|
</script>
|
|
25
33
|
|
|
26
34
|
<template>
|
|
27
35
|
<DmsEmptyState
|
|
28
36
|
size="md"
|
|
29
|
-
:variant="props.failure
|
|
37
|
+
:variant="VARIANTS[props.failure]"
|
|
30
38
|
:title="t(`dms.form.load_error.${props.failure}_title`)"
|
|
31
39
|
:description="t(`dms.form.load_error.${props.failure}_description`)"
|
|
32
40
|
>
|
|
33
|
-
<template #actions>
|
|
41
|
+
<template v-if="props.failure !== 'missing'" #actions>
|
|
34
42
|
<UButton
|
|
35
43
|
:label="t('dms.form.load_error.retry')"
|
|
36
44
|
icon="i-ph-arrows-clockwise"
|
|
@@ -52,52 +52,47 @@ import {
|
|
|
52
52
|
findServedQuickAction,
|
|
53
53
|
} from "../../utils/dispatchQuickActionTarget";
|
|
54
54
|
import {
|
|
55
|
-
|
|
55
|
+
namesPageValue,
|
|
56
56
|
resolveLinkVariables,
|
|
57
57
|
resolveTextVariables,
|
|
58
58
|
} from "../../utils/urlVariables";
|
|
59
|
-
import {
|
|
59
|
+
import { currentPageUrlValues } from "../page/pageUrlValues";
|
|
60
60
|
import { type DownloadFetch, saveDownload } from "../../utils/fileDownload";
|
|
61
|
+
import { interpolateRowText } from "#dms-core/app/utils/url-interpolation";
|
|
61
62
|
|
|
62
63
|
/** Runs a confirmed action from inside its dialog (see `onConfirm`). */
|
|
63
64
|
type ConfirmedRun = NonNullable<ConfirmOptions["onConfirm"]>;
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* A
|
|
67
|
-
*
|
|
67
|
+
* A target's URL with the page's `{{params.X}}` / `{{query.X}}` filled (a
|
|
68
|
+
* query pair without a value left out), then the row's `{{row.field}}`: an
|
|
68
69
|
* action keeps the page's context (`?website={{query.website}}`) as a page
|
|
69
|
-
* header button does
|
|
70
|
-
* as it is then.
|
|
70
|
+
* header button does, whatever its type and wherever it sits. Read when the
|
|
71
|
+
* action runs, so it carries the URL as it is then.
|
|
71
72
|
*/
|
|
72
|
-
function
|
|
73
|
-
const withPageValues =
|
|
73
|
+
function targetUrl(url: string, rowData?: Data): string {
|
|
74
|
+
const withPageValues = namesPageValue(url)
|
|
74
75
|
? resolveLinkVariables(url, currentPageUrlValues())
|
|
75
76
|
: url;
|
|
76
77
|
return rowData ? interpolateUrl(withPageValues, rowData) : withPageValues;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
|
-
* A download target's file name with the
|
|
81
|
-
* (a name, not a URL: nothing is encoded)
|
|
82
|
-
*
|
|
81
|
+
* A download target's file name with the row's `{{row.field}}` and the
|
|
82
|
+
* page's tokens filled as they are (a name, not a URL: nothing is encoded). A
|
|
83
|
+
* token without a value leaves no name: the server's one is used.
|
|
83
84
|
*/
|
|
84
85
|
function downloadTargetFilename(
|
|
85
86
|
filename: string | undefined,
|
|
86
87
|
rowData?: Data,
|
|
87
88
|
): string | undefined {
|
|
88
89
|
if (!filename) return undefined;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
return interpolateLiteral(withPageValues, rowData);
|
|
90
|
+
const withRow = rowData ? interpolateRowText(filename, rowData) : filename;
|
|
91
|
+
return resolveTextVariables(withRow, currentPageUrlValues());
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
routeParams: useSiteLayout().findMatchingRoute(route.path)?.params,
|
|
98
|
-
routeQuery: route.query as Record<string, unknown>,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
94
|
+
// The id an export job's start answered, in its `statusUrl` / `downloadUrl`.
|
|
95
|
+
const JOB_ID_TOKEN = /\{\{\s*job\.id\s*\}\}/g;
|
|
101
96
|
|
|
102
97
|
/** Where the buttons run: the requests they send, what they refresh. */
|
|
103
98
|
export interface ActionTargetsConfig {
|
|
@@ -119,7 +114,7 @@ export interface ActionTargetsConfig {
|
|
|
119
114
|
*/
|
|
120
115
|
onRecordShown?: (id?: string) => void;
|
|
121
116
|
/**
|
|
122
|
-
* The field a row's id is read from (`_id` by default): it fills `{id}` in
|
|
117
|
+
* The field a row's id is read from (`_id` by default): it fills `{{row.id}}` in
|
|
123
118
|
* the `from` URL of a row action's confirmation, as on a built-in action.
|
|
124
119
|
*/
|
|
125
120
|
rowIdKey?: string;
|
|
@@ -275,7 +270,10 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
275
270
|
const statusUrlTemplate = target.statusUrl;
|
|
276
271
|
const downloadUrlTemplate = target.downloadUrl;
|
|
277
272
|
const interpolate = (template: string, ticket: ExportJobTicket): string =>
|
|
278
|
-
|
|
273
|
+
targetUrl(
|
|
274
|
+
template.replace(JOB_ID_TOKEN, encodeURIComponent(ticket.jobId)),
|
|
275
|
+
rowData,
|
|
276
|
+
);
|
|
279
277
|
await runExportJob({
|
|
280
278
|
startUrl: url,
|
|
281
279
|
startMethod: target.method ?? "POST",
|
|
@@ -299,7 +297,7 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
299
297
|
try {
|
|
300
298
|
await saveDownload(
|
|
301
299
|
{
|
|
302
|
-
url:
|
|
300
|
+
url: targetUrl(target.url, rowData),
|
|
303
301
|
filename: downloadTargetFilename(target.filename, rowData),
|
|
304
302
|
},
|
|
305
303
|
fetchRaw,
|
|
@@ -538,11 +536,11 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
538
536
|
const targetHandlers: Record<string, TargetHandler> = {
|
|
539
537
|
page: (target, _label, rowData) => {
|
|
540
538
|
const pageTarget = target as ActionTarget & { type: "page" };
|
|
541
|
-
navigateDms(
|
|
539
|
+
navigateDms(targetUrl(pageTarget.url, rowData));
|
|
542
540
|
},
|
|
543
541
|
external: (target, _label, rowData) => {
|
|
544
542
|
const externalTarget = target as ActionTarget & { type: "external" };
|
|
545
|
-
const url =
|
|
543
|
+
const url = targetUrl(externalTarget.url, rowData);
|
|
546
544
|
if (typeof window === "undefined" || !url) return;
|
|
547
545
|
if (externalTarget.newTab) {
|
|
548
546
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
@@ -552,16 +550,12 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
552
550
|
},
|
|
553
551
|
api: async (target, label, rowData) => {
|
|
554
552
|
const apiTarget = target as ActionTarget & { type: "api" };
|
|
555
|
-
const url = rowData
|
|
556
|
-
? interpolateUrl(apiTarget.url, rowData)
|
|
557
|
-
: apiTarget.url;
|
|
553
|
+
const url = targetUrl(apiTarget.url, rowData);
|
|
558
554
|
await handleApiTarget(apiTarget, url, label, rowData);
|
|
559
555
|
},
|
|
560
556
|
exportJob: async (target, _label, rowData) => {
|
|
561
557
|
const jobTarget = target as ActionTarget & { type: "exportJob" };
|
|
562
|
-
const url = rowData
|
|
563
|
-
? interpolateUrl(jobTarget.url, rowData)
|
|
564
|
-
: jobTarget.url;
|
|
558
|
+
const url = targetUrl(jobTarget.url, rowData);
|
|
565
559
|
await handleExportJobTarget(jobTarget, url, rowData);
|
|
566
560
|
},
|
|
567
561
|
download: (target, label, rowData) =>
|
|
@@ -581,9 +575,6 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
581
575
|
sidePanel: sidePanelHandler,
|
|
582
576
|
};
|
|
583
577
|
|
|
584
|
-
const targetUrl = (url: string, rowData?: Data): string =>
|
|
585
|
-
rowData ? interpolateUrl(url, rowData) : url;
|
|
586
|
-
|
|
587
578
|
// A target making a request runs it from inside its confirmation, which
|
|
588
579
|
// shows a refusal (under its field when it names one) and stays open; any
|
|
589
580
|
// other runs once confirmed.
|
|
@@ -604,7 +595,7 @@ export function useActionTargets(config: ActionTargetsConfig) {
|
|
|
604
595
|
},
|
|
605
596
|
};
|
|
606
597
|
|
|
607
|
-
// `{id}` of a confirmation's `from` URL, when the row carries an id.
|
|
598
|
+
// `{{row.id}}` of a confirmation's `from` URL, when the row carries an id.
|
|
608
599
|
const rowUrlParams = (rowData?: Data) => {
|
|
609
600
|
const id = rowData
|
|
610
601
|
? get(rowData, config.rowIdKey ?? DEFAULT_ROW_ID_KEY)
|
|
@@ -18,9 +18,19 @@ const DATA_LABEL_FONT_SIZE = "11px";
|
|
|
18
18
|
const DATA_LABEL_FONT_WEIGHT = 600;
|
|
19
19
|
const TOOLTIP_DATETIME_FORMAT = "dd MMM yyyy";
|
|
20
20
|
const HOVER_MARKER_SIZE = 5;
|
|
21
|
+
/** Grows a visible point on hover instead of setting a size below its own. */
|
|
22
|
+
const HOVER_POINT_SIZE_OFFSET = 2;
|
|
23
|
+
/** A scatter is nothing but its points, so it draws them without `pointSize`. */
|
|
24
|
+
const DEFAULT_POINT_SIZE = 6;
|
|
25
|
+
const POINT_MARKER_TYPES: ChartType[] = ["scatter"];
|
|
26
|
+
/** Half a data label's box and a gap: lifts the label off the point it names. */
|
|
27
|
+
const POINT_LABEL_CLEARANCE = 11;
|
|
28
|
+
const HALF = 2;
|
|
21
29
|
const HEAD_MARKER_SIZE = 5;
|
|
22
30
|
const SOLID_GRID = 0;
|
|
23
31
|
const HEAD_MARKER_TYPES: ChartType[] = ["line", "area"];
|
|
32
|
+
/** A radar draws its own polygon grid; the cartesian one would cross the card. */
|
|
33
|
+
const OWN_GRID_TYPES: ChartType[] = ["radar"];
|
|
24
34
|
|
|
25
35
|
/** Theme colors every section of an Apex config reads once per build. */
|
|
26
36
|
export interface ApexThemeColors {
|
|
@@ -83,7 +93,10 @@ export function buildGrid(
|
|
|
83
93
|
theme: ApexThemeColors,
|
|
84
94
|
): Record<string, unknown> {
|
|
85
95
|
return {
|
|
86
|
-
show:
|
|
96
|
+
show:
|
|
97
|
+
!isCircular &&
|
|
98
|
+
!OWN_GRID_TYPES.includes(input.type) &&
|
|
99
|
+
(input.showGrid ?? true),
|
|
87
100
|
borderColor: theme.border,
|
|
88
101
|
strokeDashArray: SOLID_GRID,
|
|
89
102
|
padding: GRID_PADDING,
|
|
@@ -140,6 +153,13 @@ export function buildMarkers(
|
|
|
140
153
|
colors: string[],
|
|
141
154
|
theme: ApexThemeColors,
|
|
142
155
|
): Record<string, unknown> {
|
|
156
|
+
if (POINT_MARKER_TYPES.includes(input.type)) {
|
|
157
|
+
return {
|
|
158
|
+
size: pointSize(input),
|
|
159
|
+
strokeWidth: 0,
|
|
160
|
+
hover: { sizeOffset: HOVER_POINT_SIZE_OFFSET },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
143
163
|
const showsHead = HEAD_MARKER_TYPES.includes(input.type) && !input.stacked;
|
|
144
164
|
return {
|
|
145
165
|
size: 0,
|
|
@@ -151,18 +171,55 @@ export function buildMarkers(
|
|
|
151
171
|
};
|
|
152
172
|
}
|
|
153
173
|
|
|
154
|
-
|
|
174
|
+
function pointSize(input: UseApexChartInput): number {
|
|
175
|
+
return input.pointSize ?? DEFAULT_POINT_SIZE;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* How far a data label moves up: Apex centres it on its point, which hides a
|
|
180
|
+
* scatter's point under its own label.
|
|
181
|
+
*/
|
|
182
|
+
export function dataLabelOffset(input: UseApexChartInput): number {
|
|
183
|
+
if (!POINT_MARKER_TYPES.includes(input.type)) return 0;
|
|
184
|
+
return -(pointSize(input) / HALF + POINT_LABEL_CLEARANCE);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Where Apex is drawing a data label. */
|
|
188
|
+
interface DataLabelContext {
|
|
189
|
+
seriesIndex: number;
|
|
190
|
+
dataPointIndex: number;
|
|
191
|
+
w: { config: { series: ChartSeries[] } };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** A point's own `label` when it has one, else the value Apex would print. */
|
|
195
|
+
function pointLabel(value: unknown, context: DataLabelContext): unknown {
|
|
196
|
+
const point =
|
|
197
|
+
context.w.config.series[context.seriesIndex]?.data[context.dataPointIndex];
|
|
198
|
+
if (typeof point !== "object" || point === null || !("label" in point)) {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
return point.label || value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Value labels in mono, without Apex's default drop shadow. A circular chart
|
|
206
|
+
* keeps Apex's own percent formatter: its series hold bare numbers.
|
|
207
|
+
*/
|
|
155
208
|
export function buildDataLabels(
|
|
156
209
|
enabled: boolean,
|
|
157
210
|
theme: ApexThemeColors,
|
|
211
|
+
isCircular = false,
|
|
212
|
+
offsetY = 0,
|
|
158
213
|
): Record<string, unknown> {
|
|
159
214
|
return {
|
|
160
215
|
enabled,
|
|
216
|
+
offsetY,
|
|
161
217
|
dropShadow: { enabled: false },
|
|
162
218
|
style: {
|
|
163
219
|
fontSize: DATA_LABEL_FONT_SIZE,
|
|
164
220
|
fontFamily: theme.monoFont,
|
|
165
221
|
fontWeight: DATA_LABEL_FONT_WEIGHT,
|
|
166
222
|
},
|
|
223
|
+
...(isCircular ? {} : { formatter: pointLabel }),
|
|
167
224
|
};
|
|
168
225
|
}
|
|
@@ -3,7 +3,10 @@ import type {
|
|
|
3
3
|
ConfirmDialogSecondaryAction,
|
|
4
4
|
ConfirmDialogSecondaryTarget,
|
|
5
5
|
} from "#dms-core/app/types/confirm-dialog";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
interpolateRowText,
|
|
8
|
+
interpolateUrl,
|
|
9
|
+
} from "#dms-core/app/utils/url-interpolation";
|
|
7
10
|
import { readTranslationKey } from "#dms-core/app/composables/translation/useTranslation";
|
|
8
11
|
import type {
|
|
9
12
|
ConfirmOptions,
|
|
@@ -11,6 +14,12 @@ import type {
|
|
|
11
14
|
} from "../../../composables/confirm/types";
|
|
12
15
|
import { processFieldI18n } from "../../utils/fieldOptionsI18n";
|
|
13
16
|
import { interpolateLiteral } from "../../utils/literalText";
|
|
17
|
+
import {
|
|
18
|
+
namesPageValue,
|
|
19
|
+
resolveLinkVariables,
|
|
20
|
+
resolveTextVariables,
|
|
21
|
+
} from "../../utils/urlVariables";
|
|
22
|
+
import { currentPageUrlValues } from "../page/pageUrlValues";
|
|
14
23
|
|
|
15
24
|
/** `t` as vue-i18n gives it: named parameters, then the plural count. */
|
|
16
25
|
export type ConfirmTranslate = (
|
|
@@ -41,22 +50,36 @@ export type SecondaryTargetRun = (
|
|
|
41
50
|
target: ConfirmDialogSecondaryTarget,
|
|
42
51
|
) => Promise<void> | void;
|
|
43
52
|
|
|
53
|
+
// The page's `{{params.X}}` / `{{query.X}}` come first, as on an action's own
|
|
54
|
+
// target; the route is only read for a text holding such a token.
|
|
55
|
+
function withPageLink(url: string): string {
|
|
56
|
+
return namesPageValue(url)
|
|
57
|
+
? resolveLinkVariables(url, currentPageUrlValues())
|
|
58
|
+
: url;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function withPageText(text: string): string | undefined {
|
|
62
|
+
return namesPageValue(text)
|
|
63
|
+
? resolveTextVariables(text, currentPageUrlValues())
|
|
64
|
+
: text;
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
/**
|
|
45
68
|
* A dialog's secondary target with its URL (and file name) filled in with
|
|
46
|
-
* the same parameters as its texts.
|
|
69
|
+
* the page's values and, as `{{row.X}}`, the same parameters as its texts.
|
|
47
70
|
*/
|
|
48
71
|
function resolveSecondaryTarget(
|
|
49
72
|
target: ConfirmDialogSecondaryTarget,
|
|
50
73
|
params: Record<string, unknown>,
|
|
51
74
|
): ConfirmDialogSecondaryTarget {
|
|
52
|
-
const url = interpolateUrl(target.url, params);
|
|
75
|
+
const url = interpolateUrl(withPageLink(target.url), params);
|
|
53
76
|
if (target.type !== "download" || target.filename === undefined) {
|
|
54
77
|
return { ...target, url };
|
|
55
78
|
}
|
|
56
79
|
return {
|
|
57
80
|
...target,
|
|
58
81
|
url,
|
|
59
|
-
filename:
|
|
82
|
+
filename: withPageText(interpolateRowText(target.filename, params)),
|
|
60
83
|
};
|
|
61
84
|
}
|
|
62
85
|
|
|
@@ -15,12 +15,17 @@ import {
|
|
|
15
15
|
} from "./confirmDialogTexts";
|
|
16
16
|
import type { DownloadFetch } from "../../utils/fileDownload";
|
|
17
17
|
import { secondaryTargetRunner } from "./secondaryTarget";
|
|
18
|
+
import {
|
|
19
|
+
namesPageValue,
|
|
20
|
+
resolveLinkVariables,
|
|
21
|
+
} from "../../utils/urlVariables";
|
|
22
|
+
import { currentPageUrlValues } from "../page/pageUrlValues";
|
|
18
23
|
|
|
19
24
|
/** What an action asks its confirmation with. */
|
|
20
25
|
export interface ActionConfirmRequest {
|
|
21
26
|
/** The row the action targets: its fields fill the texts and `from` URL. */
|
|
22
27
|
row?: Record<string, unknown>;
|
|
23
|
-
/** Fill-ins of a `from` URL besides the row's fields (`{id}`). */
|
|
28
|
+
/** Fill-ins of a `from` URL besides the row's fields (`{{row.id}}`). */
|
|
24
29
|
urlParams?: Record<string, unknown>;
|
|
25
30
|
/**
|
|
26
31
|
* The look of the action, where the dialog leaves it out (an archive's
|
|
@@ -55,7 +60,10 @@ export function useActionConfirm() {
|
|
|
55
60
|
request: ActionConfirmRequest,
|
|
56
61
|
): Promise<ConfirmDialog | undefined> => {
|
|
57
62
|
if (!isConfirmFrom(declared)) return declared;
|
|
58
|
-
const
|
|
63
|
+
const from = namesPageValue(declared.from)
|
|
64
|
+
? resolveLinkVariables(declared.from, currentPageUrlValues())
|
|
65
|
+
: declared.from;
|
|
66
|
+
const url = interpolateUrl(from, {
|
|
59
67
|
...request.row,
|
|
60
68
|
...request.urlParams,
|
|
61
69
|
});
|
|
@@ -322,13 +322,8 @@ function renderRelation(value: unknown, options: unknown) {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
function renderCascaderRelation(value: unknown, options: unknown) {
|
|
325
|
-
const { t } = useI18n();
|
|
326
325
|
const opts = options as CascaderRelationOptions | undefined;
|
|
327
326
|
|
|
328
|
-
if (opts?.multiple && Array.isArray(value)) {
|
|
329
|
-
return t("dms.form.cascader.selected_count", { count: value.length });
|
|
330
|
-
}
|
|
331
|
-
|
|
332
327
|
if (!value) return value;
|
|
333
328
|
|
|
334
329
|
return h(CascaderPath, {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FormFieldOrGroup } from "../../../composables/form/types/field";
|
|
2
|
+
import { isFieldGroup } from "../../../composables/form/types/field";
|
|
3
|
+
|
|
4
|
+
function entryOrder(entry: FormFieldOrGroup): number | undefined {
|
|
5
|
+
return isFieldGroup(entry) ? entry.order : undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A form's entries with its groups' `order` applied: the groups giving one
|
|
10
|
+
* trade the places they were declared at, sorted on it (equal orders keep
|
|
11
|
+
* their declaration order); every other entry keeps its place.
|
|
12
|
+
*/
|
|
13
|
+
export function orderFormEntries<T extends FormFieldOrGroup>(
|
|
14
|
+
entries: readonly T[],
|
|
15
|
+
): T[] {
|
|
16
|
+
const ordered = entries
|
|
17
|
+
.filter((entry) => entryOrder(entry) !== undefined)
|
|
18
|
+
.sort((left, right) => entryOrder(left)! - entryOrder(right)!);
|
|
19
|
+
let next = 0;
|
|
20
|
+
return entries.map((entry) =>
|
|
21
|
+
entryOrder(entry) === undefined ? entry : ordered[next++]!,
|
|
22
|
+
);
|
|
23
|
+
}
|