@conduction/nextcloud-vue 2.56.0-beta.11 → 2.56.0-beta.13
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/dist/esm/components/CnAppNav/CnAppNav.vue2.js +253 -10
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +139 -46
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +87 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +26 -24
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +154 -4
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +555 -480
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +37 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +12 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +187 -19
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +215 -135
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +157 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +23 -5
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +5 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
- package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +74 -5
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +15 -8
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +513 -81
- package/dist/esm/store/useObjectStore.js +118 -33
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +25 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/schemaRefSlug.js +19 -3
- package/dist/esm/utils/schemaRefSlug.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8858 -6566
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +513 -81
- package/l10n/en.json +5 -0
- package/l10n/nl.json +5 -0
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +441 -14
- package/src/components/CnAppRoot/CnAppRoot.md +4 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +94 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +237 -12
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDataTable/CnDataTable.vue +45 -1
- package/src/components/CnDetailPage/CnDetailPage.md +8 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +263 -7
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +206 -9
- package/src/components/CnIndexPage/CnIndexPage.vue +32 -2
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +189 -2
- package/src/components/CnStagesWidget/stagesModel.js +5 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +89 -7
- package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
- package/src/css/detail-page.css +37 -4
- package/src/css/kpi-card.css +71 -1
- package/src/css/table.css +18 -6
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +162 -3
- package/src/store/useObjectStore.js +118 -33
- package/src/utils/actionsDispatcher.js +25 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/schemaRefSlug.js +19 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -41,8 +41,20 @@
|
|
|
41
41
|
{{ skipLinkLabel }}
|
|
42
42
|
</a>
|
|
43
43
|
|
|
44
|
+
<!-- Breadcrumb line (manifest `config.breadcrumb`): the list the record
|
|
45
|
+
belongs to, then the record itself as the current crumb. -->
|
|
46
|
+
<CnBreadcrumbs
|
|
47
|
+
v-if="breadcrumbCrumbs.length > 0 && !objectNotFound"
|
|
48
|
+
class="cn-detail-page__breadcrumbs"
|
|
49
|
+
:crumbs="breadcrumbCrumbs"
|
|
50
|
+
:rootText="breadcrumbCrumbs[0].icon === undefined"
|
|
51
|
+
data-testid="cn-detail-page-breadcrumbs" />
|
|
44
52
|
<!-- Header -->
|
|
45
|
-
<div
|
|
53
|
+
<div
|
|
54
|
+
v-if="!objectNotFound"
|
|
55
|
+
class="cn-detail-page__header"
|
|
56
|
+
:class="{ 'cn-detail-page__header--card': headerCard, 'cn-detail-page__header--with-widget': headerWidgetDef !== null }"
|
|
57
|
+
data-testid="cn-detail-page-header">
|
|
46
58
|
<!-- Header (left block) — overridable via #header slot. Default
|
|
47
59
|
renders the icon + title + description. The right-hand
|
|
48
60
|
#actions slot remains separate so headerComponent and
|
|
@@ -98,7 +110,7 @@
|
|
|
98
110
|
(ADR-062). Only shown once the object resolves to a
|
|
99
111
|
display name that differs from the type label. -->
|
|
100
112
|
<p
|
|
101
|
-
v-if="typeEyebrow"
|
|
113
|
+
v-if="typeEyebrow && showTypeEyebrow"
|
|
102
114
|
class="cn-detail-page__type-eyebrow"
|
|
103
115
|
data-testid="cn-detail-page-type-eyebrow">
|
|
104
116
|
{{ typeEyebrow }}
|
|
@@ -133,6 +145,18 @@
|
|
|
133
145
|
</div>
|
|
134
146
|
</slot>
|
|
135
147
|
<div class="cn-detail-page__header-actions">
|
|
148
|
+
<!-- The page's view switch (`views`), unless a greeting header
|
|
149
|
+
widget on the page draws it. Its options control the view
|
|
150
|
+
region in the body. -->
|
|
151
|
+
<CnSegmentedControl
|
|
152
|
+
v-if="showsPageViewSwitch"
|
|
153
|
+
class="cn-page-view-switch"
|
|
154
|
+
:options="viewSwitchOptions"
|
|
155
|
+
:modelValue="activeViewId"
|
|
156
|
+
:ariaLabel="viewSwitchLabel"
|
|
157
|
+
:controls="viewRegionId"
|
|
158
|
+
data-testid="cn-detail-page-view-switch"
|
|
159
|
+
@update:modelValue="selectView" />
|
|
136
160
|
<!-- Next and previous inside the list this record was opened
|
|
137
161
|
from. Rendered only when the address names that list: a
|
|
138
162
|
record reached by a bare link offers neither, rather than
|
|
@@ -363,6 +387,50 @@
|
|
|
363
387
|
</template>
|
|
364
388
|
</CnActionsMenu>
|
|
365
389
|
</div>
|
|
390
|
+
<!-- A widget inside the header (manifest `config.headerWidget`), on
|
|
391
|
+
a row of its own under the title and the actions: the stages
|
|
392
|
+
bars of the board's case card. Drawn without a card of its own,
|
|
393
|
+
and taken out of the body grid. Nothing renders without the
|
|
394
|
+
key. -->
|
|
395
|
+
<div
|
|
396
|
+
v-if="headerWidgetDef !== null"
|
|
397
|
+
class="cn-detail-page__header-widget"
|
|
398
|
+
data-testid="cn-detail-page-header-widget">
|
|
399
|
+
<!--
|
|
400
|
+
@slot `widget-${headerWidgetDef.id}`
|
|
401
|
+
@description The same per-widget slot the body grid and the side
|
|
402
|
+
column offer, for the widget placed in the header
|
|
403
|
+
(`headerWidget`). Same name and same bindings.
|
|
404
|
+
-->
|
|
405
|
+
<slot
|
|
406
|
+
:name="`widget-${headerWidgetDef.id}`"
|
|
407
|
+
:item="{ id: headerWidgetDef.id, widgetId: headerWidgetDef.id }"
|
|
408
|
+
:widget="headerWidgetDef"
|
|
409
|
+
:objectId="objectId"
|
|
410
|
+
:object="resolvedObject"
|
|
411
|
+
:objectData="resolvedObject"
|
|
412
|
+
:objectType="resolvedObjectType"
|
|
413
|
+
:register="register"
|
|
414
|
+
:schema="schema">
|
|
415
|
+
<CnDetailWidgetHost
|
|
416
|
+
:widget="headerWidgetDef"
|
|
417
|
+
chrome="bare"
|
|
418
|
+
:objectId="objectId"
|
|
419
|
+
:object="currentObject"
|
|
420
|
+
:objectType="resolvedObjectType"
|
|
421
|
+
:schemaObject="currentSchema"
|
|
422
|
+
:register="register"
|
|
423
|
+
:schema="schema"
|
|
424
|
+
:store="effectiveObjectStore"
|
|
425
|
+
:surface="surface"
|
|
426
|
+
:integrationContext="effectiveIntegrationContext"
|
|
427
|
+
:hideEmpty="hideEmpty"
|
|
428
|
+
:cnRegistry="cnRegistry"
|
|
429
|
+
:availableWidgets="widgets"
|
|
430
|
+
@geoSaved="onGeoSaved"
|
|
431
|
+
@openIntegration="onAutoBodyOpenIntegration" />
|
|
432
|
+
</slot>
|
|
433
|
+
</div>
|
|
366
434
|
</div>
|
|
367
435
|
|
|
368
436
|
<!-- The locked card, under the title, for ANY active lock — not only a
|
|
@@ -553,15 +621,19 @@
|
|
|
553
621
|
subscribed to the layout/widgets arrays — in-place pushes
|
|
554
622
|
(Add widget) keep the same array identity and can't re-render
|
|
555
623
|
it. One remount per edit flip re-subscribes it. -->
|
|
624
|
+
<!-- One grid per section: the body grid, then the chosen view
|
|
625
|
+
(`views`) in a region the view switch controls. A view grid is
|
|
626
|
+
keyed on its view, so switching mounts the other view's grid. -->
|
|
556
627
|
<CnDashboardGrid
|
|
557
|
-
v-
|
|
558
|
-
:key="`cn-detail-grid-${editingBody ? 'editing' : 'live'}`"
|
|
559
|
-
|
|
628
|
+
v-for="section in bodyGridSections"
|
|
629
|
+
:key="`cn-detail-grid-${section.key}-${editingBody ? 'editing' : 'live'}`"
|
|
630
|
+
v-bind="section.attrs"
|
|
631
|
+
:layout="section.layout"
|
|
560
632
|
:editable="editingBody"
|
|
561
633
|
:columns="12"
|
|
562
634
|
:columnOpts="columnOpts"
|
|
563
635
|
class="cn-detail-page__grid"
|
|
564
|
-
@layoutChange="onBodyLayoutChange">
|
|
636
|
+
@layoutChange="section.view ? onViewLayoutChange($event) : onBodyLayoutChange($event)">
|
|
565
637
|
<template #widget="{ item }">
|
|
566
638
|
<div
|
|
567
639
|
class="cn-detail-page__grid-item"
|
|
@@ -671,6 +743,22 @@
|
|
|
671
743
|
</template>
|
|
672
744
|
</CnDashboardGrid>
|
|
673
745
|
|
|
746
|
+
<!-- The chosen view has nothing to draw: say so in the region,
|
|
747
|
+
never leave a blank area under the switch. -->
|
|
748
|
+
<div
|
|
749
|
+
v-if="viewIsEmpty"
|
|
750
|
+
:id="viewRegionId"
|
|
751
|
+
role="region"
|
|
752
|
+
:aria-label="activeView ? activeView.label : null"
|
|
753
|
+
class="cn-page-view-region cn-page-view-region--empty"
|
|
754
|
+
data-testid="cn-detail-page-view-empty">
|
|
755
|
+
<NcEmptyContent :description="viewEmptyText">
|
|
756
|
+
<template #icon>
|
|
757
|
+
<ViewDashboardOutline :size="48" />
|
|
758
|
+
</template>
|
|
759
|
+
</NcEmptyContent>
|
|
760
|
+
</div>
|
|
761
|
+
|
|
674
762
|
<!-- Statistics table -->
|
|
675
763
|
<div v-if="hasStats" class="cn-detail-page__stats">
|
|
676
764
|
<!--
|
|
@@ -980,6 +1068,7 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
|
|
|
980
1068
|
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
981
1069
|
import Plus from 'vue-material-design-icons/Plus.vue'
|
|
982
1070
|
import Refresh from 'vue-material-design-icons/Refresh.vue'
|
|
1071
|
+
import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue'
|
|
983
1072
|
import CnRelationLinkModal from '../../dialogs/CnRelationLinkModal.vue'
|
|
984
1073
|
import CnWidgetStyleEditorModal from '../../dialogs/CnWidgetStyleEditorModal.vue'
|
|
985
1074
|
import CnActionsMenu from '../CnActionsMenu/CnActionsMenu.vue'
|
|
@@ -992,6 +1081,7 @@ import CnLifecycleActions from '../CnLifecycleActions/CnLifecycleActions.vue'
|
|
|
992
1081
|
import CnLockedBanner from '../CnLockedBanner/CnLockedBanner.vue'
|
|
993
1082
|
import CnNextStepCard from '../CnNextStepCard/CnNextStepCard.vue'
|
|
994
1083
|
import CnRelatedCollections from '../CnRelatedCollections/CnRelatedCollections.vue'
|
|
1084
|
+
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
995
1085
|
import CnSummaryAggregates from '../CnSummaryAggregates/CnSummaryAggregates.vue'
|
|
996
1086
|
import CnTranslatedBadge from '../CnTranslatedBadge/CnTranslatedBadge.vue'
|
|
997
1087
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
@@ -999,6 +1089,7 @@ import { useObjectLock } from '../../composables/useObjectLock.js'
|
|
|
999
1089
|
import { useObjectPresence } from '../../composables/useObjectPresence.js'
|
|
1000
1090
|
import { useObjectSubscription } from '../../composables/useObjectSubscription.js'
|
|
1001
1091
|
import { gridLayout } from '../../mixins/gridLayout.js'
|
|
1092
|
+
import { pageViews } from '../../mixins/pageViews.js'
|
|
1002
1093
|
import { useObjectStore } from '../../store/index.js'
|
|
1003
1094
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
1004
1095
|
import { compactLayoutRows } from '../../utils/dashboardPlacement.js'
|
|
@@ -1025,6 +1116,7 @@ import {
|
|
|
1025
1116
|
widgetTitleOf,
|
|
1026
1117
|
} from '../../utils/widgetDispatch.js'
|
|
1027
1118
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
1119
|
+
import { CnBreadcrumbs } from '../CnBreadcrumbs/index.js'
|
|
1028
1120
|
import { CnIcon } from '../CnIcon/index.js'
|
|
1029
1121
|
import { CnStatusBadge } from '../CnStatusBadge/index.js'
|
|
1030
1122
|
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
|
|
@@ -1170,8 +1262,11 @@ export default {
|
|
|
1170
1262
|
CnDetailWidgetHost,
|
|
1171
1263
|
CnFormDialog,
|
|
1172
1264
|
CnDashboardGrid,
|
|
1265
|
+
CnSegmentedControl,
|
|
1266
|
+
ViewDashboardOutline,
|
|
1173
1267
|
CnLifecycleActions,
|
|
1174
1268
|
CnActionButtons,
|
|
1269
|
+
CnBreadcrumbs,
|
|
1175
1270
|
CnStatusBadge,
|
|
1176
1271
|
CnSummaryAggregates,
|
|
1177
1272
|
CnRelatedCollections,
|
|
@@ -1184,7 +1279,7 @@ export default {
|
|
|
1184
1279
|
Pencil,
|
|
1185
1280
|
},
|
|
1186
1281
|
|
|
1187
|
-
mixins: [gridLayout],
|
|
1282
|
+
mixins: [gridLayout, pageViews],
|
|
1188
1283
|
|
|
1189
1284
|
inject: {
|
|
1190
1285
|
/**
|
|
@@ -2078,6 +2173,37 @@ export default {
|
|
|
2078
2173
|
default: null,
|
|
2079
2174
|
},
|
|
2080
2175
|
|
|
2176
|
+
/**
|
|
2177
|
+
* Whether the type eyebrow (the type label above the record name)
|
|
2178
|
+
* renders once the record resolves. `false` (manifest
|
|
2179
|
+
* `config.showTypeEyebrow: false`) drops it for a header that says
|
|
2180
|
+
* the type in a pill instead.
|
|
2181
|
+
*
|
|
2182
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
2183
|
+
*/
|
|
2184
|
+
showTypeEyebrow: {
|
|
2185
|
+
type: Boolean,
|
|
2186
|
+
default: true,
|
|
2187
|
+
},
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* A breadcrumb line above the header (manifest `config.breadcrumb`):
|
|
2191
|
+
* `{ label, route?, params?, href? }` names the list the record
|
|
2192
|
+
* belongs to; the record's display name follows as the current
|
|
2193
|
+
* crumb. `label` goes through the host translate function and shows
|
|
2194
|
+
* as text; `icon` (an MDI name, e.g. `Home`) draws that icon instead,
|
|
2195
|
+
* with the label as its accessible name. Null (the default) draws no
|
|
2196
|
+
* trail.
|
|
2197
|
+
*
|
|
2198
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
2199
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text
|
|
2200
|
+
* @type {{label: string, route?: string, params?: object, href?: string, icon?: string}|null}
|
|
2201
|
+
*/
|
|
2202
|
+
breadcrumb: {
|
|
2203
|
+
type: Object,
|
|
2204
|
+
default: null,
|
|
2205
|
+
},
|
|
2206
|
+
|
|
2081
2207
|
/**
|
|
2082
2208
|
* A column of cards beside the body (manifest `config.sideColumn`),
|
|
2083
2209
|
* for the record's facts: deadline, requester, handler, links. Each
|
|
@@ -2093,6 +2219,33 @@ export default {
|
|
|
2093
2219
|
default: () => [],
|
|
2094
2220
|
},
|
|
2095
2221
|
|
|
2222
|
+
/**
|
|
2223
|
+
* Draw the header as a card (manifest `config.headerCard: true`): the
|
|
2224
|
+
* pills, the title and the actions on the page's surface colour inside
|
|
2225
|
+
* a bordered, rounded box, as the board's case header. Off by default,
|
|
2226
|
+
* which keeps the header on the page ground.
|
|
2227
|
+
*
|
|
2228
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
|
|
2229
|
+
*/
|
|
2230
|
+
headerCard: {
|
|
2231
|
+
type: Boolean,
|
|
2232
|
+
default: false,
|
|
2233
|
+
},
|
|
2234
|
+
|
|
2235
|
+
/**
|
|
2236
|
+
* The id of a widget declared in `widgets` to render inside the header,
|
|
2237
|
+
* on its own row under the title and the actions (manifest
|
|
2238
|
+
* `config.headerWidget`), e.g. the stages bars. It renders without a
|
|
2239
|
+
* card of its own and leaves the body grid. Empty (the default), or an
|
|
2240
|
+
* id that names no widget, renders nothing extra.
|
|
2241
|
+
*
|
|
2242
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
|
|
2243
|
+
*/
|
|
2244
|
+
headerWidget: {
|
|
2245
|
+
type: String,
|
|
2246
|
+
default: '',
|
|
2247
|
+
},
|
|
2248
|
+
|
|
2096
2249
|
/**
|
|
2097
2250
|
* Whether the current user administers this instance. Shows the
|
|
2098
2251
|
* `adminOnly` header actions. `null` (the default) reads it from
|
|
@@ -2632,6 +2785,21 @@ export default {
|
|
|
2632
2785
|
.filter(Boolean)
|
|
2633
2786
|
},
|
|
2634
2787
|
|
|
2788
|
+
/**
|
|
2789
|
+
* The widget the header holds (`headerWidget`): the definition in
|
|
2790
|
+
* `widgets` with that id, or null without the key or a match.
|
|
2791
|
+
*
|
|
2792
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
|
|
2793
|
+
* @return {object|null} The widget definition, or null.
|
|
2794
|
+
*/
|
|
2795
|
+
headerWidgetDef() {
|
|
2796
|
+
if (!this.headerWidget) {
|
|
2797
|
+
return null
|
|
2798
|
+
}
|
|
2799
|
+
const declared = Array.isArray(this.widgets) ? this.widgets : []
|
|
2800
|
+
return declared.find((widget) => widget && widget.id === this.headerWidget) || null
|
|
2801
|
+
},
|
|
2802
|
+
|
|
2635
2803
|
/**
|
|
2636
2804
|
* The side column's widget definitions. A string names a widget
|
|
2637
2805
|
* declared in `widgets`; an object is a definition of its own.
|
|
@@ -3201,6 +3369,33 @@ export default {
|
|
|
3201
3369
|
return this.objectDisplayName || this.resolvedTitle
|
|
3202
3370
|
},
|
|
3203
3371
|
|
|
3372
|
+
/**
|
|
3373
|
+
* The breadcrumb trail: the declared crumb (a router target from
|
|
3374
|
+
* `route` + `params`, or an `href`), then the record's display name
|
|
3375
|
+
* as the current crumb. Empty without a `breadcrumb` label. The first
|
|
3376
|
+
* crumb carries `icon` only when the manifest declares one.
|
|
3377
|
+
*
|
|
3378
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
3379
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text
|
|
3380
|
+
* @return {Array<{label: string, to?: object, href?: string, icon?: string}>}
|
|
3381
|
+
*/
|
|
3382
|
+
breadcrumbCrumbs() {
|
|
3383
|
+
const crumb = this.breadcrumb
|
|
3384
|
+
if (!crumb || typeof crumb.label !== 'string' || crumb.label === '') {
|
|
3385
|
+
return []
|
|
3386
|
+
}
|
|
3387
|
+
const first = { label: this.effectiveTranslate(crumb.label) }
|
|
3388
|
+
if (typeof crumb.icon === 'string' && crumb.icon !== '') {
|
|
3389
|
+
first.icon = crumb.icon
|
|
3390
|
+
}
|
|
3391
|
+
if (crumb.route) {
|
|
3392
|
+
first.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) }
|
|
3393
|
+
} else if (crumb.href) {
|
|
3394
|
+
first.href = crumb.href
|
|
3395
|
+
}
|
|
3396
|
+
return [first, { label: this.displayTitle }]
|
|
3397
|
+
},
|
|
3398
|
+
|
|
3204
3399
|
/**
|
|
3205
3400
|
* Effective translate function: the injected `cnTranslate` (the host
|
|
3206
3401
|
* app's bound `t()`), identity by default.
|
|
@@ -3304,6 +3499,7 @@ export default {
|
|
|
3304
3499
|
&& this.currentObject
|
|
3305
3500
|
&& !this.hasDefaultSlotContent
|
|
3306
3501
|
&& !this.hasGridLayout
|
|
3502
|
+
&& !this.hasViews
|
|
3307
3503
|
},
|
|
3308
3504
|
|
|
3309
3505
|
/**
|
|
@@ -3383,6 +3579,55 @@ export default {
|
|
|
3383
3579
|
return this.bodyGridLayout.length > 0
|
|
3384
3580
|
},
|
|
3385
3581
|
|
|
3582
|
+
/**
|
|
3583
|
+
* The chosen view's layout as the grid draws it: items that draw
|
|
3584
|
+
* nothing leave and their rows close, as for the body grid.
|
|
3585
|
+
*
|
|
3586
|
+
* @return {Array<object>}
|
|
3587
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
3588
|
+
*/
|
|
3589
|
+
viewDisplayLayout() {
|
|
3590
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- reading the flag IS the effect, as in bodyGridLayout
|
|
3591
|
+
this.editingBody
|
|
3592
|
+
return this.activeView ? this.closeDroppedRows(this.activeView.layout) : []
|
|
3593
|
+
},
|
|
3594
|
+
|
|
3595
|
+
/**
|
|
3596
|
+
* Whether the chosen view has nothing to draw.
|
|
3597
|
+
*
|
|
3598
|
+
* @return {boolean}
|
|
3599
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
3600
|
+
*/
|
|
3601
|
+
viewIsEmpty() {
|
|
3602
|
+
return this.hasViews && this.viewDisplayLayout.length === 0
|
|
3603
|
+
},
|
|
3604
|
+
|
|
3605
|
+
/**
|
|
3606
|
+
* The grids the body draws: the body grid when it has items, then the
|
|
3607
|
+
* chosen view's grid in the region the view switch controls (left out
|
|
3608
|
+
* when the view is empty; the empty state takes the region then).
|
|
3609
|
+
*
|
|
3610
|
+
* @return {Array<{key: string, view: boolean, layout: Array<object>, attrs: object}>}
|
|
3611
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
3612
|
+
*/
|
|
3613
|
+
bodyGridSections() {
|
|
3614
|
+
const sections = this.hasBodyGrid ? [{ key: 'body', view: false, layout: this.bodyGridLayout, attrs: {} }] : []
|
|
3615
|
+
if (this.hasViews && !this.viewIsEmpty) {
|
|
3616
|
+
sections.push({
|
|
3617
|
+
key: `view-${this.activeViewId}`,
|
|
3618
|
+
view: true,
|
|
3619
|
+
layout: this.viewDisplayLayout,
|
|
3620
|
+
attrs: {
|
|
3621
|
+
id: this.viewRegionId,
|
|
3622
|
+
role: 'region',
|
|
3623
|
+
'aria-label': this.activeView.label,
|
|
3624
|
+
'data-testid': 'cn-detail-page-view-region',
|
|
3625
|
+
},
|
|
3626
|
+
})
|
|
3627
|
+
}
|
|
3628
|
+
return sections
|
|
3629
|
+
},
|
|
3630
|
+
|
|
3386
3631
|
/**
|
|
3387
3632
|
* Whether the sidebar is rendered externally (via objectSidebarState inject)
|
|
3388
3633
|
* rather than inline. When external, CnDetailPage only manages state —
|
|
@@ -4604,6 +4849,8 @@ export default {
|
|
|
4604
4849
|
*/
|
|
4605
4850
|
findWidget(item) {
|
|
4606
4851
|
return this.bodyGridWidgets.find((w) => w.id === item.widgetId)
|
|
4852
|
+
|| this.findViewWidget(item.widgetId)
|
|
4853
|
+
|| undefined
|
|
4607
4854
|
},
|
|
4608
4855
|
|
|
4609
4856
|
/**
|
|
@@ -4652,6 +4899,10 @@ export default {
|
|
|
4652
4899
|
* it — CnPageRenderer's split pane forwards no slots, which is exactly
|
|
4653
4900
|
* that fault and wants fixing, not concealing.
|
|
4654
4901
|
*
|
|
4902
|
+
* The widget the header holds (`headerWidget`) draws nothing here
|
|
4903
|
+
* either: it is already drawn in the header.
|
|
4904
|
+
*
|
|
4905
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
|
|
4655
4906
|
* @param {object} item A layout item.
|
|
4656
4907
|
* @return {boolean} False only when the item provably draws nothing.
|
|
4657
4908
|
*/
|
|
@@ -4662,6 +4913,11 @@ export default {
|
|
|
4662
4913
|
if (this.editingBody) {
|
|
4663
4914
|
return true
|
|
4664
4915
|
}
|
|
4916
|
+
// The widget the header holds (`headerWidget`) is drawn there, so
|
|
4917
|
+
// its grid cell would be the same widget twice.
|
|
4918
|
+
if (this.headerWidgetDef !== null && item.widgetId === this.headerWidgetDef.id) {
|
|
4919
|
+
return false
|
|
4920
|
+
}
|
|
4665
4921
|
const widget = this.findWidget(item)
|
|
4666
4922
|
if (!widget) {
|
|
4667
4923
|
return true
|
|
@@ -135,7 +135,9 @@ export default {
|
|
|
135
135
|
if (!store) {
|
|
136
136
|
return
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
// `schema` is a slug the manifest author wrote, not a `$ref` title:
|
|
139
|
+
// use it exactly as given (a camelCase slug must not be kebab-cased).
|
|
140
|
+
const type = resolveObjectOpType(store, { register: this.register, schema: this.schema }, { exactSchema: true })
|
|
139
141
|
await Promise.all(this.ids.map(async (id) => {
|
|
140
142
|
if (this.labels[id]) {
|
|
141
143
|
return
|
|
@@ -513,6 +513,7 @@ import { draftKey, formDraftMixin, readDraft } from '../../composables/useFormDr
|
|
|
513
513
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
514
514
|
import { TENANT_CONTEXT_KEY } from '../../composables/useTenantContext.js'
|
|
515
515
|
import { useObjectStore } from '../../store/useObjectStore.js'
|
|
516
|
+
import { resolveCreateOverrideHandler } from '../../utils/actionsDispatcher.js'
|
|
516
517
|
import {
|
|
517
518
|
definitionQueryParams,
|
|
518
519
|
DYNAMIC_KEY_PREFIX,
|
|
@@ -757,6 +758,18 @@ export default {
|
|
|
757
758
|
* identity when used standalone (no CnAppRoot ancestor).
|
|
758
759
|
*/
|
|
759
760
|
cnTranslate: { default: () => (key) => key },
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* The app manifest, the v2 registry, the legacy customComponents map and
|
|
764
|
+
* the registry-modal opener, all provided by CnAppRoot. A select-or-create
|
|
765
|
+
* picker reads them to create an object the way the app's own index page
|
|
766
|
+
* does (`config.createOverride` / `config.createModal`). Standalone (no
|
|
767
|
+
* CnAppRoot) they are absent and the generic nested form is used.
|
|
768
|
+
*/
|
|
769
|
+
cnManifest: { default: null },
|
|
770
|
+
cnRegistry: { default: null },
|
|
771
|
+
cnCustomComponents: { default: null },
|
|
772
|
+
cnOpenModal: { default: null },
|
|
760
773
|
},
|
|
761
774
|
|
|
762
775
|
props: {
|
|
@@ -2649,6 +2662,16 @@ export default {
|
|
|
2649
2662
|
store.registerObjectType(slug, schemaRef, register)
|
|
2650
2663
|
}
|
|
2651
2664
|
const labelField = this.referenceLabelField(field)
|
|
2665
|
+
// The app may own how this schema is created (pipelinq's client and
|
|
2666
|
+
// contact need a contact-first endpoint; a plain save 400s). A
|
|
2667
|
+
// create override keeps this form and saves through the app's
|
|
2668
|
+
// handler; a create modal (only when no override exists) opens the
|
|
2669
|
+
// app's own dialog instead of the generic form.
|
|
2670
|
+
const appCreate = this.appCreateFor(register, schemaRef)
|
|
2671
|
+
if (!appCreate.override && appCreate.modal) {
|
|
2672
|
+
return this.openAppCreateModal(appCreate.modal, store, slug, labelField, term)
|
|
2673
|
+
}
|
|
2674
|
+
const override = appCreate.override
|
|
2652
2675
|
let schema = null
|
|
2653
2676
|
try {
|
|
2654
2677
|
schema = await store.fetchSchema(slug)
|
|
@@ -2656,14 +2679,113 @@ export default {
|
|
|
2656
2679
|
schema = null
|
|
2657
2680
|
}
|
|
2658
2681
|
if (!schema || !schema.properties) {
|
|
2659
|
-
|
|
2682
|
+
const payload = { [labelField]: term }
|
|
2683
|
+
return override
|
|
2684
|
+
? override(payload, { register, schema: schemaRef, objectType: slug, effectiveSchema: null })
|
|
2685
|
+
: store.saveObject(slug, payload)
|
|
2660
2686
|
}
|
|
2661
2687
|
const initialData = {}
|
|
2662
2688
|
if (term && schema.properties[labelField]) {
|
|
2663
2689
|
initialData[labelField] = term
|
|
2664
2690
|
}
|
|
2665
2691
|
return new Promise((resolve) => {
|
|
2666
|
-
this.nestedCreate = { field, schema, register, slug, initialData, resolve }
|
|
2692
|
+
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, resolve }
|
|
2693
|
+
})
|
|
2694
|
+
},
|
|
2695
|
+
|
|
2696
|
+
/**
|
|
2697
|
+
* How the app creates objects of a schema, read off the manifest page
|
|
2698
|
+
* that lists them: the index page whose `config.schema` (and, when it
|
|
2699
|
+
* names one, `config.register`) matches. Its `config.createOverride` is
|
|
2700
|
+
* resolved to the registered handler; its `config.createModal` is the
|
|
2701
|
+
* registry key of a `kind: 'modal'` dialog.
|
|
2702
|
+
*
|
|
2703
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
2704
|
+
* @param {string} register The reference's register.
|
|
2705
|
+
* @param {string} schemaRef The reference's schema slug or id.
|
|
2706
|
+
* @return {{override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null)}} What the app declared.
|
|
2707
|
+
*/
|
|
2708
|
+
appCreateFor(register, schemaRef) {
|
|
2709
|
+
const none = { override: null, modal: null }
|
|
2710
|
+
const pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []
|
|
2711
|
+
const wanted = String(schemaRef).toLowerCase()
|
|
2712
|
+
const page = pages.find((p) => {
|
|
2713
|
+
const config = p && p.config
|
|
2714
|
+
if (!config || config.schema === undefined || config.schema === null) {
|
|
2715
|
+
return false
|
|
2716
|
+
}
|
|
2717
|
+
if (String(config.schema).toLowerCase() !== wanted) {
|
|
2718
|
+
return false
|
|
2719
|
+
}
|
|
2720
|
+
return !config.register || String(config.register) === String(register)
|
|
2721
|
+
})
|
|
2722
|
+
if (!page) {
|
|
2723
|
+
return none
|
|
2724
|
+
}
|
|
2725
|
+
const config = page.config
|
|
2726
|
+
const override = typeof config.createOverride === 'function'
|
|
2727
|
+
? config.createOverride
|
|
2728
|
+
: resolveCreateOverrideHandler(config.createOverride, this.cnRegistry, this.cnCustomComponents)
|
|
2729
|
+
const modalEntry = typeof config.createModal === 'string' && config.createModal !== ''
|
|
2730
|
+
? (this.cnRegistry || {})[config.createModal]
|
|
2731
|
+
: null
|
|
2732
|
+
const modal = modalEntry && modalEntry.kind === 'modal' && typeof this.cnOpenModal === 'function'
|
|
2733
|
+
? config.createModal
|
|
2734
|
+
: null
|
|
2735
|
+
return { override: override || null, modal }
|
|
2736
|
+
},
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* Open the app's own create dialog (a registry modal) for a picker and
|
|
2740
|
+
* wait for it. The dialog reports the new object (or its id) with a
|
|
2741
|
+
* `created` event; `close` without one resolves null so the field stays
|
|
2742
|
+
* as it was. A reported id is fetched so the picker can label it.
|
|
2743
|
+
*
|
|
2744
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
2745
|
+
* @param {string} key The registry key of the modal.
|
|
2746
|
+
* @param {object} store The object store.
|
|
2747
|
+
* @param {string} slug The object type slug.
|
|
2748
|
+
* @param {string} labelField The property the typed term belongs in.
|
|
2749
|
+
* @param {string} term The typed search term.
|
|
2750
|
+
* @return {Promise<object|null>} The created object, or null.
|
|
2751
|
+
*/
|
|
2752
|
+
openAppCreateModal(key, store, slug, labelField, term) {
|
|
2753
|
+
return new Promise((resolve) => {
|
|
2754
|
+
let settled = false
|
|
2755
|
+
let reported = false
|
|
2756
|
+
const settle = (value) => {
|
|
2757
|
+
if (!settled) {
|
|
2758
|
+
settled = true
|
|
2759
|
+
resolve(value)
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
this.cnOpenModal(key, {
|
|
2763
|
+
initialData: term ? { [labelField]: term } : {},
|
|
2764
|
+
onCreated: async (created) => {
|
|
2765
|
+
if (created && typeof created === 'object') {
|
|
2766
|
+
settle(created)
|
|
2767
|
+
return
|
|
2768
|
+
}
|
|
2769
|
+
if (typeof created !== 'string' && typeof created !== 'number') {
|
|
2770
|
+
return
|
|
2771
|
+
}
|
|
2772
|
+
reported = true
|
|
2773
|
+
let obj
|
|
2774
|
+
try {
|
|
2775
|
+
obj = await store.fetchObject(slug, String(created))
|
|
2776
|
+
} catch {
|
|
2777
|
+
obj = null
|
|
2778
|
+
}
|
|
2779
|
+
settle(obj || { id: String(created), [labelField]: term })
|
|
2780
|
+
},
|
|
2781
|
+
onClose: () => {
|
|
2782
|
+
// A dialog often emits `close` right after `created`,
|
|
2783
|
+
// while the id is still being fetched: that fetch settles.
|
|
2784
|
+
if (!reported) {
|
|
2785
|
+
settle(null)
|
|
2786
|
+
}
|
|
2787
|
+
},
|
|
2788
|
+
})
|
|
2667
2789
|
})
|
|
2668
2790
|
},
|
|
2669
2791
|
|
|
@@ -2682,10 +2804,28 @@ export default {
|
|
|
2682
2804
|
return
|
|
2683
2805
|
}
|
|
2684
2806
|
const store = this.getObjectStore()
|
|
2685
|
-
|
|
2807
|
+
let created = null
|
|
2808
|
+
let thrown = null
|
|
2809
|
+
if (pending.override) {
|
|
2810
|
+
// The app's create override owns persistence (same contract as
|
|
2811
|
+
// CnIndexPage's `createOverride`).
|
|
2812
|
+
try {
|
|
2813
|
+
created = await pending.override(formData, {
|
|
2814
|
+
register: pending.register,
|
|
2815
|
+
schema: pending.schemaRef,
|
|
2816
|
+
objectType: pending.slug,
|
|
2817
|
+
effectiveSchema: pending.schema,
|
|
2818
|
+
})
|
|
2819
|
+
} catch (err) {
|
|
2820
|
+
thrown = (err && err.response && err.response.data && err.response.data.error) || (err && err.message) || null
|
|
2821
|
+
}
|
|
2822
|
+
} else {
|
|
2823
|
+
created = store ? await store.saveObject(pending.slug, formData) : null
|
|
2824
|
+
}
|
|
2686
2825
|
if (!created) {
|
|
2687
2826
|
const error = store && store.errors ? store.errors[pending.slug] : null
|
|
2688
|
-
const message =
|
|
2827
|
+
const message = thrown
|
|
2828
|
+
|| (!pending.override && error && error.message)
|
|
2689
2829
|
|| t('nextcloud-vue', 'The item could not be created.')
|
|
2690
2830
|
const dialog = this.$refs.nestedCreateDialog
|
|
2691
2831
|
if (dialog && typeof dialog.setValidationErrors === 'function') {
|