@conduction/nextcloud-vue 2.62.0 → 2.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +425 -39
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
- 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/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/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +253 -9
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +41 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.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 +52 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
- 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/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.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/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +45 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +45 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +780 -81
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- 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/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +13088 -7937
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +780 -81
- package/l10n/en.json +45 -0
- package/l10n/nl.json +45 -0
- package/package.json +1 -1
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +568 -43
- package/src/components/CnAppRoot/CnAppRoot.md +4 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +305 -5
- package/src/components/CnDetailPage/CnDetailPage.md +4 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +116 -6
- package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
- package/src/components/CnStagesWidget/stagesModel.js +5 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +29 -4
- package/src/css/kpi-card.css +24 -1
- package/src/css/table.css +99 -6
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +135 -3
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/schemaRefSlug.js +19 -3
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -19,6 +19,36 @@
|
|
|
19
19
|
</span>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
+
<!-- Active header filters, one removable chip each. -->
|
|
23
|
+
<ul
|
|
24
|
+
v-if="!isLoading && activeColumnFilterChips.length > 0"
|
|
25
|
+
class="cn-data-table__filter-chips"
|
|
26
|
+
:aria-label="activeFiltersLabel"
|
|
27
|
+
data-testid="cn-table-filter-chips">
|
|
28
|
+
<li v-for="chip in activeColumnFilterChips" :key="chip.key" class="cn-data-table__filter-chip">
|
|
29
|
+
<span>{{ chip.text }}</span>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="cn-data-table__filter-chip-remove"
|
|
33
|
+
:aria-label="chip.removeLabel"
|
|
34
|
+
data-testid="cn-table-filter-chip-remove"
|
|
35
|
+
@click="clearColumnFilter(chip.col)">
|
|
36
|
+
×
|
|
37
|
+
</button>
|
|
38
|
+
</li>
|
|
39
|
+
</ul>
|
|
40
|
+
|
|
41
|
+
<CnColumnFilterPopover
|
|
42
|
+
v-if="openFilterColumn"
|
|
43
|
+
:key="openFilterKey"
|
|
44
|
+
:def="filterDefFor(openFilterColumn)"
|
|
45
|
+
:state="columnFilterStateFor(openFilterColumn)"
|
|
46
|
+
:label="translateLabel(openFilterColumn.label)"
|
|
47
|
+
:anchorRect="filterAnchorRect"
|
|
48
|
+
:register="filterRegister"
|
|
49
|
+
@apply="(state) => applyColumnFilter(openFilterColumn, state)"
|
|
50
|
+
@close="closeColumnFilter" />
|
|
51
|
+
|
|
22
52
|
<!-- Loading State -->
|
|
23
53
|
<div v-if="isLoading" class="cn-table-loading" data-testid="cn-object-list-loading">
|
|
24
54
|
<!-- Decorative: the adjacent <p> already carries the accessible
|
|
@@ -92,6 +122,7 @@
|
|
|
92
122
|
:style="col.width ? { width: col.width } : {}"
|
|
93
123
|
:tabindex="col.sortable ? 0 : null"
|
|
94
124
|
:aria-sort="ariaSortFor(col)"
|
|
125
|
+
:data-filtered="isColumnFiltered(col) ? 'true' : null"
|
|
95
126
|
:title="translateLabel(col.description) || null"
|
|
96
127
|
@click="col.sortable ? onHeaderClick(col.key, $event) : null"
|
|
97
128
|
@keydown.enter="col.sortable ? onHeaderKeydown(col.key, $event) : null">
|
|
@@ -108,6 +139,23 @@
|
|
|
108
139
|
class="cn-table-sort-badge">
|
|
109
140
|
{{ sortBadgeFor(col.key) }}
|
|
110
141
|
</span>
|
|
142
|
+
<!-- Header filter: a real button, so it is reachable with Tab
|
|
143
|
+
and its click and Enter never also sort the column. -->
|
|
144
|
+
<button
|
|
145
|
+
v-if="filterDefFor(col)"
|
|
146
|
+
:ref="'filterButton-' + col.key"
|
|
147
|
+
type="button"
|
|
148
|
+
class="cn-table-header__filter"
|
|
149
|
+
:class="{ 'cn-table-header__filter--active': isColumnFiltered(col) }"
|
|
150
|
+
:aria-label="filterButtonLabel(col)"
|
|
151
|
+
aria-haspopup="dialog"
|
|
152
|
+
:aria-expanded="openFilterKey === col.key ? 'true' : 'false'"
|
|
153
|
+
data-testid="cn-table-header-filter"
|
|
154
|
+
@click.stop="toggleColumnFilter(col, $event)"
|
|
155
|
+
@keydown.enter.stop>
|
|
156
|
+
<FilterIcon v-if="isColumnFiltered(col)" :size="16" />
|
|
157
|
+
<FilterOutline v-else :size="16" />
|
|
158
|
+
</button>
|
|
111
159
|
</th>
|
|
112
160
|
|
|
113
161
|
<!-- Actions column -->
|
|
@@ -230,6 +278,14 @@
|
|
|
230
278
|
:row="row"
|
|
231
279
|
:rowKey="rowKey" />
|
|
232
280
|
</slot>
|
|
281
|
+
<!-- A second, muted line under the value, from `col.secondary`:
|
|
282
|
+
a field key, a "{field} · {other}" template, or a function
|
|
283
|
+
of the row. Lets one column read "title" over "number ·
|
|
284
|
+
requester" without a custom cell. -->
|
|
285
|
+
<span
|
|
286
|
+
v-if="secondaryValue(row, col)"
|
|
287
|
+
class="cn-table-cell__secondary"
|
|
288
|
+
data-testid="cn-cell-secondary">{{ secondaryValue(row, col) }}</span>
|
|
233
289
|
</td>
|
|
234
290
|
|
|
235
291
|
<!-- Row actions -->
|
|
@@ -283,10 +339,14 @@
|
|
|
283
339
|
|
|
284
340
|
<script>
|
|
285
341
|
import axios from '@nextcloud/axios'
|
|
286
|
-
import { translate as t } from '@nextcloud/l10n'
|
|
342
|
+
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
287
343
|
import { generateUrl } from '@nextcloud/router'
|
|
288
344
|
import { NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
345
|
+
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
|
346
|
+
import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
|
|
347
|
+
import CnColumnFilterPopover from './CnColumnFilterPopover.vue'
|
|
289
348
|
import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
|
|
349
|
+
import { clearedColumnFilterParams, columnFilterDef, columnFilterParams, columnFilterState, isColumnFilterActive, isColumnSortable } from '../../utils/columnFilters.js'
|
|
290
350
|
import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
|
|
291
351
|
import { nextSortState } from '../../utils/multiColumnSort.js'
|
|
292
352
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
@@ -357,8 +417,11 @@ export default {
|
|
|
357
417
|
NcLoadingIcon,
|
|
358
418
|
NcCheckboxRadioSwitch,
|
|
359
419
|
CnCellRenderer,
|
|
420
|
+
CnColumnFilterPopover,
|
|
360
421
|
CnIcon,
|
|
361
422
|
CnLockIndicator,
|
|
423
|
+
FilterIcon,
|
|
424
|
+
FilterOutline,
|
|
362
425
|
},
|
|
363
426
|
|
|
364
427
|
inject: {
|
|
@@ -389,7 +452,13 @@ export default {
|
|
|
389
452
|
* reader is looking. `columnsFromSchema` fills it from the JSON Schema property
|
|
390
453
|
* description automatically, so schema-driven tables get it for free.
|
|
391
454
|
*
|
|
392
|
-
*
|
|
455
|
+
* `secondary` draws a second, muted line under the cell's value: a field
|
|
456
|
+
* key (`"number"`), a template with `{field}` placeholders
|
|
457
|
+
* (`"{number} · {requester}"`, dotted paths allowed), or a function of
|
|
458
|
+
* the row. Empty fields leave their placeholder blank; a line that
|
|
459
|
+
* resolves to nothing is not drawn.
|
|
460
|
+
*
|
|
461
|
+
* @type {Array<{key: string, label: string, description: string, sortable: boolean, width: string, class: string, cellClass: string, secondary: (string|((row: object) => string))}|string>}
|
|
393
462
|
*/
|
|
394
463
|
columns: {
|
|
395
464
|
type: Array,
|
|
@@ -727,9 +796,39 @@ export default {
|
|
|
727
796
|
type: Boolean,
|
|
728
797
|
default: false,
|
|
729
798
|
},
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Show a filter button in every header whose column can filter (see
|
|
802
|
+
* `columnFilterDef`): enum, boolean, text, number, date and reference
|
|
803
|
+
* columns backed by a schema property. A column opts out with
|
|
804
|
+
* `filterable: false`. Applying a filter emits `column-filter`; the
|
|
805
|
+
* host owns the filter state and passes it back as `activeFilters`.
|
|
806
|
+
*
|
|
807
|
+
* @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header
|
|
808
|
+
*/
|
|
809
|
+
filterable: {
|
|
810
|
+
type: Boolean,
|
|
811
|
+
default: false,
|
|
812
|
+
},
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* The active filter map, `{ paramKey: values[] }`: the same map the
|
|
816
|
+
* facet sidebar writes, so a header filter and a sidebar filter on one
|
|
817
|
+
* field show the same state.
|
|
818
|
+
*/
|
|
819
|
+
activeFilters: {
|
|
820
|
+
type: Object,
|
|
821
|
+
default: () => ({}),
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
/** Register slug a reference column's filter searches in when the column names none. */
|
|
825
|
+
filterRegister: {
|
|
826
|
+
type: String,
|
|
827
|
+
default: '',
|
|
828
|
+
},
|
|
730
829
|
},
|
|
731
830
|
|
|
732
|
-
emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all'],
|
|
831
|
+
emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all', 'column-filter'],
|
|
733
832
|
|
|
734
833
|
setup() {
|
|
735
834
|
// Tell a deliberate row click apart from a text-selection drag.
|
|
@@ -738,6 +837,10 @@ export default {
|
|
|
738
837
|
|
|
739
838
|
data() {
|
|
740
839
|
return {
|
|
840
|
+
/** Key of the column whose filter panel is open, or ''. */
|
|
841
|
+
openFilterKey: '',
|
|
842
|
+
/** The open filter button's bounding rect, to place the panel. */
|
|
843
|
+
filterAnchorRect: null,
|
|
741
844
|
/**
|
|
742
845
|
* Resolved aggregate-column values, keyed by `String(row[rowKey])`
|
|
743
846
|
* then by column key. Populated by `loadAggregates()` for columns
|
|
@@ -855,8 +958,12 @@ export default {
|
|
|
855
958
|
overrides: this.columnOverrides,
|
|
856
959
|
})
|
|
857
960
|
: this.columns
|
|
961
|
+
// Every column backed by a schema property sorts unless it says
|
|
962
|
+
// `sortable: false`; object columns from a manifest used to need an
|
|
963
|
+
// explicit flag and so could not sort at all.
|
|
964
|
+
const withSort = (c) => (c && typeof c === 'object' ? { ...c, sortable: isColumnSortable(c, this.schema) } : c)
|
|
858
965
|
if (!(cols || []).some((c) => typeof c === 'string')) {
|
|
859
|
-
return cols || []
|
|
966
|
+
return (cols || []).map(withSort)
|
|
860
967
|
}
|
|
861
968
|
const schemaCols = this.schema
|
|
862
969
|
? columnsFromSchema(this.schema, { overrides: this.columnOverrides })
|
|
@@ -864,7 +971,7 @@ export default {
|
|
|
864
971
|
const byKey = new Map(schemaCols.map((c) => [c.key, c]))
|
|
865
972
|
return (cols || []).map((c) => {
|
|
866
973
|
if (typeof c !== 'string') {
|
|
867
|
-
return c
|
|
974
|
+
return withSort(c)
|
|
868
975
|
}
|
|
869
976
|
return byKey.get(c) || { key: c, label: c, sortable: true }
|
|
870
977
|
})
|
|
@@ -905,6 +1012,40 @@ export default {
|
|
|
905
1012
|
return links
|
|
906
1013
|
},
|
|
907
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* The column whose filter panel is open, or null.
|
|
1017
|
+
*
|
|
1018
|
+
* @return {object|null}
|
|
1019
|
+
*/
|
|
1020
|
+
openFilterColumn() {
|
|
1021
|
+
if (!this.openFilterKey) {
|
|
1022
|
+
return null
|
|
1023
|
+
}
|
|
1024
|
+
return this.effectiveColumns.find((c) => c && c.key === this.openFilterKey && this.filterDefFor(c)) || null
|
|
1025
|
+
},
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* One chip per column with an active header filter.
|
|
1029
|
+
*
|
|
1030
|
+
* @return {Array<{key: string, col: object, text: string, removeLabel: string}>}
|
|
1031
|
+
*/
|
|
1032
|
+
activeColumnFilterChips() {
|
|
1033
|
+
if (!this.filterable) {
|
|
1034
|
+
return []
|
|
1035
|
+
}
|
|
1036
|
+
return this.effectiveColumns
|
|
1037
|
+
.filter((c) => c && this.isColumnFiltered(c))
|
|
1038
|
+
.map((col) => {
|
|
1039
|
+
const label = this.translateLabel(col.label)
|
|
1040
|
+
const text = t('nextcloud-vue', '{column}: {value}', { column: label, value: this.columnFilterSummary(col) })
|
|
1041
|
+
return { key: col.key, col, text, removeLabel: t('nextcloud-vue', 'Remove filter {column}', { column: label }) }
|
|
1042
|
+
})
|
|
1043
|
+
},
|
|
1044
|
+
|
|
1045
|
+
activeFiltersLabel() {
|
|
1046
|
+
return t('nextcloud-vue', 'Active filters')
|
|
1047
|
+
},
|
|
1048
|
+
|
|
908
1049
|
effectiveSortKeys() {
|
|
909
1050
|
if (this.sortKeys && this.sortKeys.length > 0) {
|
|
910
1051
|
return this.sortKeys
|
|
@@ -1288,6 +1429,36 @@ export default {
|
|
|
1288
1429
|
* @param {object} col The column definition.
|
|
1289
1430
|
* @return {unknown} The value handed to the slot / CnCellRenderer.
|
|
1290
1431
|
*/
|
|
1432
|
+
/**
|
|
1433
|
+
* The secondary line of a cell, from `col.secondary`: a function is
|
|
1434
|
+
* called with the row; a string with `{field}` placeholders is a
|
|
1435
|
+
* template filled from the row (dotted paths allowed); any other
|
|
1436
|
+
* string is a field key. Returns '' when there is nothing to draw.
|
|
1437
|
+
*
|
|
1438
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-table-column-carries-a-secondary-line
|
|
1439
|
+
* @param {object} row The row.
|
|
1440
|
+
* @param {object} col The column definition.
|
|
1441
|
+
* @return {string} The secondary text, or ''.
|
|
1442
|
+
*/
|
|
1443
|
+
secondaryValue(row, col) {
|
|
1444
|
+
const spec = col?.secondary
|
|
1445
|
+
if (typeof spec === 'function') {
|
|
1446
|
+
const out = spec(row)
|
|
1447
|
+
return out === null || out === undefined ? '' : String(out)
|
|
1448
|
+
}
|
|
1449
|
+
if (typeof spec !== 'string' || spec === '') {
|
|
1450
|
+
return ''
|
|
1451
|
+
}
|
|
1452
|
+
const asText = (v) => (v === null || v === undefined ? '' : String(v))
|
|
1453
|
+
if (spec.includes('{')) {
|
|
1454
|
+
const filled = spec.replace(/\{([^}]+)\}/g, (_, key) => asText(this.getCellValue(row, key.trim())))
|
|
1455
|
+
// A template whose every field is empty leaves only separators:
|
|
1456
|
+
// nothing worth a line.
|
|
1457
|
+
return /[\p{L}\p{N}]/u.test(filled) ? filled.trim() : ''
|
|
1458
|
+
}
|
|
1459
|
+
return asText(this.getCellValue(row, spec))
|
|
1460
|
+
},
|
|
1461
|
+
|
|
1291
1462
|
cellValue(row, col) {
|
|
1292
1463
|
if (col && col.aggregate) {
|
|
1293
1464
|
const cached = this.aggregateValues[String(row[this.rowKey])]
|
|
@@ -1576,6 +1747,135 @@ export default {
|
|
|
1576
1747
|
* @param {string} key Column key.
|
|
1577
1748
|
* @param {MouseEvent} [event] The originating click event.
|
|
1578
1749
|
*/
|
|
1750
|
+
/**
|
|
1751
|
+
* The filter definition for a column, or null when it does not filter
|
|
1752
|
+
* (header filters off, `filterable: false`, or nothing to filter on).
|
|
1753
|
+
*
|
|
1754
|
+
* @param {object} col The column.
|
|
1755
|
+
* @return {object|null}
|
|
1756
|
+
*/
|
|
1757
|
+
filterDefFor(col) {
|
|
1758
|
+
if (!this.filterable || !col) {
|
|
1759
|
+
return null
|
|
1760
|
+
}
|
|
1761
|
+
return columnFilterDef(col, this.schema)
|
|
1762
|
+
},
|
|
1763
|
+
|
|
1764
|
+
columnFilterStateFor(col) {
|
|
1765
|
+
return columnFilterState(this.filterDefFor(col), this.activeFilters)
|
|
1766
|
+
},
|
|
1767
|
+
|
|
1768
|
+
isColumnFiltered(col) {
|
|
1769
|
+
const def = this.filterDefFor(col)
|
|
1770
|
+
return !!def && isColumnFilterActive(def, this.activeFilters)
|
|
1771
|
+
},
|
|
1772
|
+
|
|
1773
|
+
filterButtonLabel(col) {
|
|
1774
|
+
const column = this.translateLabel(col.label)
|
|
1775
|
+
return this.isColumnFiltered(col)
|
|
1776
|
+
? t('nextcloud-vue', 'Filter {column}, active', { column })
|
|
1777
|
+
: t('nextcloud-vue', 'Filter {column}', { column })
|
|
1778
|
+
},
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* Short text for an active filter: the picked labels, or the range.
|
|
1782
|
+
*
|
|
1783
|
+
* @param {object} col The column.
|
|
1784
|
+
* @return {string}
|
|
1785
|
+
*/
|
|
1786
|
+
columnFilterSummary(col) {
|
|
1787
|
+
const def = this.filterDefFor(col)
|
|
1788
|
+
const state = this.columnFilterStateFor(col)
|
|
1789
|
+
if (!def) {
|
|
1790
|
+
return ''
|
|
1791
|
+
}
|
|
1792
|
+
if (def.kind === 'enum') {
|
|
1793
|
+
const labels = Object.fromEntries((def.options || []).map((o) => [o.value, o.label]))
|
|
1794
|
+
return state.values.map((v) => this.translateLabel(labels[v] || v)).join(', ')
|
|
1795
|
+
}
|
|
1796
|
+
if (def.kind === 'reference') {
|
|
1797
|
+
return n('nextcloud-vue', '{count} selected', '{count} selected', state.values.length, { count: state.values.length })
|
|
1798
|
+
}
|
|
1799
|
+
if (def.kind === 'number' || def.kind === 'date') {
|
|
1800
|
+
if (state.from && state.to) {
|
|
1801
|
+
return t('nextcloud-vue', '{from} to {to}', { from: state.from, to: state.to })
|
|
1802
|
+
}
|
|
1803
|
+
return state.from
|
|
1804
|
+
? t('nextcloud-vue', 'from {value}', { value: state.from })
|
|
1805
|
+
: t('nextcloud-vue', 'up to {value}', { value: state.to })
|
|
1806
|
+
}
|
|
1807
|
+
if (def.kind === 'boolean') {
|
|
1808
|
+
return state.value === 'true' ? t('nextcloud-vue', 'Yes') : t('nextcloud-vue', 'No')
|
|
1809
|
+
}
|
|
1810
|
+
return state.value
|
|
1811
|
+
},
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* Open or close a column's filter panel under its button.
|
|
1815
|
+
*
|
|
1816
|
+
* @param {object} col The column.
|
|
1817
|
+
* @param {Event} event The click.
|
|
1818
|
+
* @return {void}
|
|
1819
|
+
*/
|
|
1820
|
+
toggleColumnFilter(col, event) {
|
|
1821
|
+
if (this.openFilterKey === col.key) {
|
|
1822
|
+
this.closeColumnFilter()
|
|
1823
|
+
return
|
|
1824
|
+
}
|
|
1825
|
+
const target = event && event.currentTarget
|
|
1826
|
+
this.filterAnchorRect = target && typeof target.getBoundingClientRect === 'function'
|
|
1827
|
+
? target.getBoundingClientRect()
|
|
1828
|
+
: null
|
|
1829
|
+
this.openFilterKey = col.key
|
|
1830
|
+
},
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* Close the open panel and give focus back to its button.
|
|
1834
|
+
*
|
|
1835
|
+
* @return {void}
|
|
1836
|
+
*/
|
|
1837
|
+
closeColumnFilter() {
|
|
1838
|
+
const key = this.openFilterKey
|
|
1839
|
+
this.openFilterKey = ''
|
|
1840
|
+
if (!key) {
|
|
1841
|
+
return
|
|
1842
|
+
}
|
|
1843
|
+
this.$nextTick(() => {
|
|
1844
|
+
const ref = this.$refs['filterButton-' + key]
|
|
1845
|
+
const button = Array.isArray(ref) ? ref[0] : ref
|
|
1846
|
+
if (button && typeof button.focus === 'function') {
|
|
1847
|
+
button.focus()
|
|
1848
|
+
}
|
|
1849
|
+
})
|
|
1850
|
+
},
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Apply a panel's state: emit the query parameters it stands for.
|
|
1854
|
+
*
|
|
1855
|
+
* @param {object} col The column.
|
|
1856
|
+
* @param {object} state The panel state.
|
|
1857
|
+
* @return {void}
|
|
1858
|
+
* @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language
|
|
1859
|
+
*/
|
|
1860
|
+
applyColumnFilter(col, state) {
|
|
1861
|
+
const def = this.filterDefFor(col)
|
|
1862
|
+
if (def) {
|
|
1863
|
+
/**
|
|
1864
|
+
* @event column-filter A header filter was applied or cleared.
|
|
1865
|
+
* @type {{ key: string, params: object }} `params` maps each query parameter the column owns to its values; an empty list clears it.
|
|
1866
|
+
*/
|
|
1867
|
+
this.$emit('column-filter', { key: col.key, params: columnFilterParams(def, state) })
|
|
1868
|
+
}
|
|
1869
|
+
this.closeColumnFilter()
|
|
1870
|
+
},
|
|
1871
|
+
|
|
1872
|
+
clearColumnFilter(col) {
|
|
1873
|
+
const def = this.filterDefFor(col)
|
|
1874
|
+
if (def) {
|
|
1875
|
+
this.$emit('column-filter', { key: col.key, params: clearedColumnFilterParams(def) })
|
|
1876
|
+
}
|
|
1877
|
+
},
|
|
1878
|
+
|
|
1579
1879
|
onHeaderClick(key, event) {
|
|
1580
1880
|
this.applySort(key, !!(event && event.shiftKey))
|
|
1581
1881
|
},
|
|
@@ -430,3 +430,7 @@ Four levels of action and the case surfaces around them. All opt in. See [the re
|
|
|
430
430
|
## Not found
|
|
431
431
|
|
|
432
432
|
When the schema-driven fetch answers 404 the page shows a not-found state whose back button is a router link to `notFoundRoute` (default `null`, which means the app root `/`). `notFoundRouteLabel` names that page in the button's "Back to …" label.
|
|
433
|
+
|
|
434
|
+
### Type eyebrow and breadcrumb
|
|
435
|
+
|
|
436
|
+
`showTypeEyebrow` (default `true`) can drop the type label above the record name. `breadcrumb` (`{ label, route?, params?, href? }`) draws a breadcrumb line above the header: that crumb, then the record's name as the current crumb.
|
|
@@ -41,6 +41,13 @@
|
|
|
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
|
+
data-testid="cn-detail-page-breadcrumbs" />
|
|
44
51
|
<!-- Header -->
|
|
45
52
|
<div v-if="!objectNotFound" class="cn-detail-page__header" data-testid="cn-detail-page-header">
|
|
46
53
|
<!-- Header (left block) — overridable via #header slot. Default
|
|
@@ -98,7 +105,7 @@
|
|
|
98
105
|
(ADR-062). Only shown once the object resolves to a
|
|
99
106
|
display name that differs from the type label. -->
|
|
100
107
|
<p
|
|
101
|
-
v-if="typeEyebrow"
|
|
108
|
+
v-if="typeEyebrow && showTypeEyebrow"
|
|
102
109
|
class="cn-detail-page__type-eyebrow"
|
|
103
110
|
data-testid="cn-detail-page-type-eyebrow">
|
|
104
111
|
{{ typeEyebrow }}
|
|
@@ -1025,6 +1032,7 @@ import {
|
|
|
1025
1032
|
widgetTitleOf,
|
|
1026
1033
|
} from '../../utils/widgetDispatch.js'
|
|
1027
1034
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
1035
|
+
import { CnBreadcrumbs } from '../CnBreadcrumbs/index.js'
|
|
1028
1036
|
import { CnIcon } from '../CnIcon/index.js'
|
|
1029
1037
|
import { CnStatusBadge } from '../CnStatusBadge/index.js'
|
|
1030
1038
|
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
|
|
@@ -1172,6 +1180,7 @@ export default {
|
|
|
1172
1180
|
CnDashboardGrid,
|
|
1173
1181
|
CnLifecycleActions,
|
|
1174
1182
|
CnActionButtons,
|
|
1183
|
+
CnBreadcrumbs,
|
|
1175
1184
|
CnStatusBadge,
|
|
1176
1185
|
CnSummaryAggregates,
|
|
1177
1186
|
CnRelatedCollections,
|
|
@@ -2078,6 +2087,34 @@ export default {
|
|
|
2078
2087
|
default: null,
|
|
2079
2088
|
},
|
|
2080
2089
|
|
|
2090
|
+
/**
|
|
2091
|
+
* Whether the type eyebrow (the type label above the record name)
|
|
2092
|
+
* renders once the record resolves. `false` (manifest
|
|
2093
|
+
* `config.showTypeEyebrow: false`) drops it for a header that says
|
|
2094
|
+
* the type in a pill instead.
|
|
2095
|
+
*
|
|
2096
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
2097
|
+
*/
|
|
2098
|
+
showTypeEyebrow: {
|
|
2099
|
+
type: Boolean,
|
|
2100
|
+
default: true,
|
|
2101
|
+
},
|
|
2102
|
+
|
|
2103
|
+
/**
|
|
2104
|
+
* A breadcrumb line above the header (manifest `config.breadcrumb`):
|
|
2105
|
+
* `{ label, route?, params?, href? }` names the list the record
|
|
2106
|
+
* belongs to; the record's display name follows as the current
|
|
2107
|
+
* crumb. `label` goes through the host translate function. Null (the
|
|
2108
|
+
* default) draws no trail.
|
|
2109
|
+
*
|
|
2110
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
2111
|
+
* @type {{label: string, route?: string, params?: object, href?: string}|null}
|
|
2112
|
+
*/
|
|
2113
|
+
breadcrumb: {
|
|
2114
|
+
type: Object,
|
|
2115
|
+
default: null,
|
|
2116
|
+
},
|
|
2117
|
+
|
|
2081
2118
|
/**
|
|
2082
2119
|
* A column of cards beside the body (manifest `config.sideColumn`),
|
|
2083
2120
|
* for the record's facts: deadline, requester, handler, links. Each
|
|
@@ -3201,6 +3238,28 @@ export default {
|
|
|
3201
3238
|
return this.objectDisplayName || this.resolvedTitle
|
|
3202
3239
|
},
|
|
3203
3240
|
|
|
3241
|
+
/**
|
|
3242
|
+
* The breadcrumb trail: the declared crumb (a router target from
|
|
3243
|
+
* `route` + `params`, or an `href`), then the record's display name
|
|
3244
|
+
* as the current crumb. Empty without a `breadcrumb` label.
|
|
3245
|
+
*
|
|
3246
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
|
|
3247
|
+
* @return {Array<{label: string, to?: object, href?: string}>}
|
|
3248
|
+
*/
|
|
3249
|
+
breadcrumbCrumbs() {
|
|
3250
|
+
const crumb = this.breadcrumb
|
|
3251
|
+
if (!crumb || typeof crumb.label !== 'string' || crumb.label === '') {
|
|
3252
|
+
return []
|
|
3253
|
+
}
|
|
3254
|
+
const first = { label: this.effectiveTranslate(crumb.label) }
|
|
3255
|
+
if (crumb.route) {
|
|
3256
|
+
first.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) }
|
|
3257
|
+
} else if (crumb.href) {
|
|
3258
|
+
first.href = crumb.href
|
|
3259
|
+
}
|
|
3260
|
+
return [first, { label: this.displayTitle }]
|
|
3261
|
+
},
|
|
3262
|
+
|
|
3204
3263
|
/**
|
|
3205
3264
|
* Effective translate function: the injected `cnTranslate` (the host
|
|
3206
3265
|
* app's bound `t()`), identity by default.
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
<template v-if="widget && widget.icon" #title-icon>
|
|
75
75
|
<CnIcon :name="widget.icon" :size="20" />
|
|
76
76
|
</template>
|
|
77
|
-
<NcEmptyContent :name="missingAppName" :description="missingAppDescription">
|
|
77
|
+
<NcEmptyContent class="cn-requires-app" :name="missingAppName" :description="missingAppDescription">
|
|
78
78
|
<template #icon>
|
|
79
|
-
<CnIcon :name="(widget && widget.icon) || 'PuzzleOutline'" :size="
|
|
79
|
+
<CnIcon :name="(widget && widget.icon) || 'PuzzleOutline'" :size="32" />
|
|
80
80
|
</template>
|
|
81
81
|
</NcEmptyContent>
|
|
82
82
|
</CnWidgetWrapper>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!-- Keeps the legacy `cn-form-dialog__helper` class alongside its own so
|
|
3
3
|
consumer stylesheets that target the old helper span still apply. -->
|
|
4
4
|
<span
|
|
5
|
-
v-if="error || text"
|
|
5
|
+
v-if="error || text || more"
|
|
6
6
|
class="cn-field-helper cn-form-dialog__helper"
|
|
7
7
|
:class="{ 'cn-field-helper--error': !!error, 'cn-form-dialog__helper--error': !!error }">
|
|
8
8
|
{{ error || text }}
|
|
@@ -42,7 +42,9 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
|
|
|
42
42
|
* `fieldsFromSchema` splits it and puts the full text on the field's
|
|
43
43
|
* `descriptionLong`; pass that as `more` and this renders an ⓘ button that
|
|
44
44
|
* reveals it in a popover, so a paragraph-length schema description cannot
|
|
45
|
-
* push the rest of the form off screen.
|
|
45
|
+
* push the rest of the form off screen. A schema property's `x-help` text
|
|
46
|
+
* also lands on `descriptionLong`, so the ⓘ shows even when the description
|
|
47
|
+
* is short or absent.
|
|
46
48
|
*
|
|
47
49
|
* `CnFormDialog` uses this for every auto-generated field. Use it directly when
|
|
48
50
|
* rendering your own fields through the `#form-fields` or `#field-<key>` slots,
|