@conduction/nextcloud-vue 2.62.0 → 2.63.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 +271 -32
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.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.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- 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 +216 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
- package/dist/esm/components/CnDataTable/CnDataTable.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/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- 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 +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- 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/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/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 +43 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +43 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +449 -0
- 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/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 +9369 -5193
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +449 -0
- package/l10n/en.json +43 -0
- package/l10n/nl.json +43 -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 +271 -32
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +260 -4
- 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/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- 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/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 +19 -0
- package/src/css/table.css +81 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- 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/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 -->
|
|
@@ -283,10 +331,14 @@
|
|
|
283
331
|
|
|
284
332
|
<script>
|
|
285
333
|
import axios from '@nextcloud/axios'
|
|
286
|
-
import { translate as t } from '@nextcloud/l10n'
|
|
334
|
+
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
287
335
|
import { generateUrl } from '@nextcloud/router'
|
|
288
336
|
import { NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
337
|
+
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
|
338
|
+
import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
|
|
339
|
+
import CnColumnFilterPopover from './CnColumnFilterPopover.vue'
|
|
289
340
|
import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
|
|
341
|
+
import { clearedColumnFilterParams, columnFilterDef, columnFilterParams, columnFilterState, isColumnFilterActive, isColumnSortable } from '../../utils/columnFilters.js'
|
|
290
342
|
import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
|
|
291
343
|
import { nextSortState } from '../../utils/multiColumnSort.js'
|
|
292
344
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
@@ -357,8 +409,11 @@ export default {
|
|
|
357
409
|
NcLoadingIcon,
|
|
358
410
|
NcCheckboxRadioSwitch,
|
|
359
411
|
CnCellRenderer,
|
|
412
|
+
CnColumnFilterPopover,
|
|
360
413
|
CnIcon,
|
|
361
414
|
CnLockIndicator,
|
|
415
|
+
FilterIcon,
|
|
416
|
+
FilterOutline,
|
|
362
417
|
},
|
|
363
418
|
|
|
364
419
|
inject: {
|
|
@@ -727,9 +782,39 @@ export default {
|
|
|
727
782
|
type: Boolean,
|
|
728
783
|
default: false,
|
|
729
784
|
},
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Show a filter button in every header whose column can filter (see
|
|
788
|
+
* `columnFilterDef`): enum, boolean, text, number, date and reference
|
|
789
|
+
* columns backed by a schema property. A column opts out with
|
|
790
|
+
* `filterable: false`. Applying a filter emits `column-filter`; the
|
|
791
|
+
* host owns the filter state and passes it back as `activeFilters`.
|
|
792
|
+
*
|
|
793
|
+
* @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header
|
|
794
|
+
*/
|
|
795
|
+
filterable: {
|
|
796
|
+
type: Boolean,
|
|
797
|
+
default: false,
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* The active filter map, `{ paramKey: values[] }`: the same map the
|
|
802
|
+
* facet sidebar writes, so a header filter and a sidebar filter on one
|
|
803
|
+
* field show the same state.
|
|
804
|
+
*/
|
|
805
|
+
activeFilters: {
|
|
806
|
+
type: Object,
|
|
807
|
+
default: () => ({}),
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
/** Register slug a reference column's filter searches in when the column names none. */
|
|
811
|
+
filterRegister: {
|
|
812
|
+
type: String,
|
|
813
|
+
default: '',
|
|
814
|
+
},
|
|
730
815
|
},
|
|
731
816
|
|
|
732
|
-
emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all'],
|
|
817
|
+
emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all', 'column-filter'],
|
|
733
818
|
|
|
734
819
|
setup() {
|
|
735
820
|
// Tell a deliberate row click apart from a text-selection drag.
|
|
@@ -738,6 +823,10 @@ export default {
|
|
|
738
823
|
|
|
739
824
|
data() {
|
|
740
825
|
return {
|
|
826
|
+
/** Key of the column whose filter panel is open, or ''. */
|
|
827
|
+
openFilterKey: '',
|
|
828
|
+
/** The open filter button's bounding rect, to place the panel. */
|
|
829
|
+
filterAnchorRect: null,
|
|
741
830
|
/**
|
|
742
831
|
* Resolved aggregate-column values, keyed by `String(row[rowKey])`
|
|
743
832
|
* then by column key. Populated by `loadAggregates()` for columns
|
|
@@ -855,8 +944,12 @@ export default {
|
|
|
855
944
|
overrides: this.columnOverrides,
|
|
856
945
|
})
|
|
857
946
|
: this.columns
|
|
947
|
+
// Every column backed by a schema property sorts unless it says
|
|
948
|
+
// `sortable: false`; object columns from a manifest used to need an
|
|
949
|
+
// explicit flag and so could not sort at all.
|
|
950
|
+
const withSort = (c) => (c && typeof c === 'object' ? { ...c, sortable: isColumnSortable(c, this.schema) } : c)
|
|
858
951
|
if (!(cols || []).some((c) => typeof c === 'string')) {
|
|
859
|
-
return cols || []
|
|
952
|
+
return (cols || []).map(withSort)
|
|
860
953
|
}
|
|
861
954
|
const schemaCols = this.schema
|
|
862
955
|
? columnsFromSchema(this.schema, { overrides: this.columnOverrides })
|
|
@@ -864,7 +957,7 @@ export default {
|
|
|
864
957
|
const byKey = new Map(schemaCols.map((c) => [c.key, c]))
|
|
865
958
|
return (cols || []).map((c) => {
|
|
866
959
|
if (typeof c !== 'string') {
|
|
867
|
-
return c
|
|
960
|
+
return withSort(c)
|
|
868
961
|
}
|
|
869
962
|
return byKey.get(c) || { key: c, label: c, sortable: true }
|
|
870
963
|
})
|
|
@@ -905,6 +998,40 @@ export default {
|
|
|
905
998
|
return links
|
|
906
999
|
},
|
|
907
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* The column whose filter panel is open, or null.
|
|
1003
|
+
*
|
|
1004
|
+
* @return {object|null}
|
|
1005
|
+
*/
|
|
1006
|
+
openFilterColumn() {
|
|
1007
|
+
if (!this.openFilterKey) {
|
|
1008
|
+
return null
|
|
1009
|
+
}
|
|
1010
|
+
return this.effectiveColumns.find((c) => c && c.key === this.openFilterKey && this.filterDefFor(c)) || null
|
|
1011
|
+
},
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* One chip per column with an active header filter.
|
|
1015
|
+
*
|
|
1016
|
+
* @return {Array<{key: string, col: object, text: string, removeLabel: string}>}
|
|
1017
|
+
*/
|
|
1018
|
+
activeColumnFilterChips() {
|
|
1019
|
+
if (!this.filterable) {
|
|
1020
|
+
return []
|
|
1021
|
+
}
|
|
1022
|
+
return this.effectiveColumns
|
|
1023
|
+
.filter((c) => c && this.isColumnFiltered(c))
|
|
1024
|
+
.map((col) => {
|
|
1025
|
+
const label = this.translateLabel(col.label)
|
|
1026
|
+
const text = t('nextcloud-vue', '{column}: {value}', { column: label, value: this.columnFilterSummary(col) })
|
|
1027
|
+
return { key: col.key, col, text, removeLabel: t('nextcloud-vue', 'Remove filter {column}', { column: label }) }
|
|
1028
|
+
})
|
|
1029
|
+
},
|
|
1030
|
+
|
|
1031
|
+
activeFiltersLabel() {
|
|
1032
|
+
return t('nextcloud-vue', 'Active filters')
|
|
1033
|
+
},
|
|
1034
|
+
|
|
908
1035
|
effectiveSortKeys() {
|
|
909
1036
|
if (this.sortKeys && this.sortKeys.length > 0) {
|
|
910
1037
|
return this.sortKeys
|
|
@@ -1576,6 +1703,135 @@ export default {
|
|
|
1576
1703
|
* @param {string} key Column key.
|
|
1577
1704
|
* @param {MouseEvent} [event] The originating click event.
|
|
1578
1705
|
*/
|
|
1706
|
+
/**
|
|
1707
|
+
* The filter definition for a column, or null when it does not filter
|
|
1708
|
+
* (header filters off, `filterable: false`, or nothing to filter on).
|
|
1709
|
+
*
|
|
1710
|
+
* @param {object} col The column.
|
|
1711
|
+
* @return {object|null}
|
|
1712
|
+
*/
|
|
1713
|
+
filterDefFor(col) {
|
|
1714
|
+
if (!this.filterable || !col) {
|
|
1715
|
+
return null
|
|
1716
|
+
}
|
|
1717
|
+
return columnFilterDef(col, this.schema)
|
|
1718
|
+
},
|
|
1719
|
+
|
|
1720
|
+
columnFilterStateFor(col) {
|
|
1721
|
+
return columnFilterState(this.filterDefFor(col), this.activeFilters)
|
|
1722
|
+
},
|
|
1723
|
+
|
|
1724
|
+
isColumnFiltered(col) {
|
|
1725
|
+
const def = this.filterDefFor(col)
|
|
1726
|
+
return !!def && isColumnFilterActive(def, this.activeFilters)
|
|
1727
|
+
},
|
|
1728
|
+
|
|
1729
|
+
filterButtonLabel(col) {
|
|
1730
|
+
const column = this.translateLabel(col.label)
|
|
1731
|
+
return this.isColumnFiltered(col)
|
|
1732
|
+
? t('nextcloud-vue', 'Filter {column}, active', { column })
|
|
1733
|
+
: t('nextcloud-vue', 'Filter {column}', { column })
|
|
1734
|
+
},
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* Short text for an active filter: the picked labels, or the range.
|
|
1738
|
+
*
|
|
1739
|
+
* @param {object} col The column.
|
|
1740
|
+
* @return {string}
|
|
1741
|
+
*/
|
|
1742
|
+
columnFilterSummary(col) {
|
|
1743
|
+
const def = this.filterDefFor(col)
|
|
1744
|
+
const state = this.columnFilterStateFor(col)
|
|
1745
|
+
if (!def) {
|
|
1746
|
+
return ''
|
|
1747
|
+
}
|
|
1748
|
+
if (def.kind === 'enum') {
|
|
1749
|
+
const labels = Object.fromEntries((def.options || []).map((o) => [o.value, o.label]))
|
|
1750
|
+
return state.values.map((v) => this.translateLabel(labels[v] || v)).join(', ')
|
|
1751
|
+
}
|
|
1752
|
+
if (def.kind === 'reference') {
|
|
1753
|
+
return n('nextcloud-vue', '{count} selected', '{count} selected', state.values.length, { count: state.values.length })
|
|
1754
|
+
}
|
|
1755
|
+
if (def.kind === 'number' || def.kind === 'date') {
|
|
1756
|
+
if (state.from && state.to) {
|
|
1757
|
+
return t('nextcloud-vue', '{from} to {to}', { from: state.from, to: state.to })
|
|
1758
|
+
}
|
|
1759
|
+
return state.from
|
|
1760
|
+
? t('nextcloud-vue', 'from {value}', { value: state.from })
|
|
1761
|
+
: t('nextcloud-vue', 'up to {value}', { value: state.to })
|
|
1762
|
+
}
|
|
1763
|
+
if (def.kind === 'boolean') {
|
|
1764
|
+
return state.value === 'true' ? t('nextcloud-vue', 'Yes') : t('nextcloud-vue', 'No')
|
|
1765
|
+
}
|
|
1766
|
+
return state.value
|
|
1767
|
+
},
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Open or close a column's filter panel under its button.
|
|
1771
|
+
*
|
|
1772
|
+
* @param {object} col The column.
|
|
1773
|
+
* @param {Event} event The click.
|
|
1774
|
+
* @return {void}
|
|
1775
|
+
*/
|
|
1776
|
+
toggleColumnFilter(col, event) {
|
|
1777
|
+
if (this.openFilterKey === col.key) {
|
|
1778
|
+
this.closeColumnFilter()
|
|
1779
|
+
return
|
|
1780
|
+
}
|
|
1781
|
+
const target = event && event.currentTarget
|
|
1782
|
+
this.filterAnchorRect = target && typeof target.getBoundingClientRect === 'function'
|
|
1783
|
+
? target.getBoundingClientRect()
|
|
1784
|
+
: null
|
|
1785
|
+
this.openFilterKey = col.key
|
|
1786
|
+
},
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* Close the open panel and give focus back to its button.
|
|
1790
|
+
*
|
|
1791
|
+
* @return {void}
|
|
1792
|
+
*/
|
|
1793
|
+
closeColumnFilter() {
|
|
1794
|
+
const key = this.openFilterKey
|
|
1795
|
+
this.openFilterKey = ''
|
|
1796
|
+
if (!key) {
|
|
1797
|
+
return
|
|
1798
|
+
}
|
|
1799
|
+
this.$nextTick(() => {
|
|
1800
|
+
const ref = this.$refs['filterButton-' + key]
|
|
1801
|
+
const button = Array.isArray(ref) ? ref[0] : ref
|
|
1802
|
+
if (button && typeof button.focus === 'function') {
|
|
1803
|
+
button.focus()
|
|
1804
|
+
}
|
|
1805
|
+
})
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* Apply a panel's state: emit the query parameters it stands for.
|
|
1810
|
+
*
|
|
1811
|
+
* @param {object} col The column.
|
|
1812
|
+
* @param {object} state The panel state.
|
|
1813
|
+
* @return {void}
|
|
1814
|
+
* @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language
|
|
1815
|
+
*/
|
|
1816
|
+
applyColumnFilter(col, state) {
|
|
1817
|
+
const def = this.filterDefFor(col)
|
|
1818
|
+
if (def) {
|
|
1819
|
+
/**
|
|
1820
|
+
* @event column-filter A header filter was applied or cleared.
|
|
1821
|
+
* @type {{ key: string, params: object }} `params` maps each query parameter the column owns to its values; an empty list clears it.
|
|
1822
|
+
*/
|
|
1823
|
+
this.$emit('column-filter', { key: col.key, params: columnFilterParams(def, state) })
|
|
1824
|
+
}
|
|
1825
|
+
this.closeColumnFilter()
|
|
1826
|
+
},
|
|
1827
|
+
|
|
1828
|
+
clearColumnFilter(col) {
|
|
1829
|
+
const def = this.filterDefFor(col)
|
|
1830
|
+
if (def) {
|
|
1831
|
+
this.$emit('column-filter', { key: col.key, params: clearedColumnFilterParams(def) })
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
|
|
1579
1835
|
onHeaderClick(key, event) {
|
|
1580
1836
|
this.applySort(key, !!(event && event.shiftKey))
|
|
1581
1837
|
},
|
|
@@ -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,
|