@conduction/nextcloud-vue 2.93.0 → 2.94.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/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +91 -36
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +115 -72
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +20 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +402 -262
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +5779 -4827
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +402 -262
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +96 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +39 -10
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +3 -3
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +31 -2
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +14 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +27 -0
- package/src/css/page-header.css +16 -0
- package/src/schemas/app-manifest-v2.schema.json +22 -15
- package/src/utils/dueText.js +49 -8
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
col.class || '',
|
|
131
131
|
pinClass(leadingCount + colIndex),
|
|
132
132
|
isGrowColumn(col) ? 'cn-table-col--grow' : '',
|
|
133
|
-
colIndex ===
|
|
133
|
+
colIndex === titleColumnIndex ? 'cn-table-head--title' : '',
|
|
134
134
|
]"
|
|
135
135
|
:style="{ ...(col.width ? { width: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
136
136
|
:aria-sort="ariaSortFor(col)"
|
|
@@ -259,7 +259,7 @@
|
|
|
259
259
|
<td
|
|
260
260
|
v-for="(col, colIndex) in effectiveColumns"
|
|
261
261
|
:key="col.key"
|
|
262
|
-
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex ===
|
|
262
|
+
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex === titleColumnIndex ? 'cn-table-col--title' : '', isGrowColumn(col) ? 'cn-table-col--grow' : '']"
|
|
263
263
|
:style="{ ...(col.width ? { maxWidth: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
264
264
|
@mouseenter="titleWhenClipped">
|
|
265
265
|
<!-- A row with a `rowClickRoute` is a real link: this anchor
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
the URL, a middle or ctrl click opens a new tab natively,
|
|
268
268
|
and the row is reachable with Tab. The row's own click
|
|
269
269
|
handlers treat it as a nested control and leave it be. -->
|
|
270
|
-
<a v-if="colIndex ===
|
|
270
|
+
<a v-if="colIndex === titleColumnIndex && rowLinks[String(row[rowKey])]"
|
|
271
271
|
class="cn-table-row__link"
|
|
272
272
|
:href="rowLinks[String(row[rowKey])].href"
|
|
273
273
|
:aria-label="rowLinkLabel(row, col)"
|
|
@@ -281,9 +281,9 @@
|
|
|
281
281
|
the record is locked. It renders nothing when unlocked,
|
|
282
282
|
so an unlocked table is byte-for-byte what it was. -->
|
|
283
283
|
<CnUnreadMarker
|
|
284
|
-
v-if="colIndex ===
|
|
284
|
+
v-if="colIndex === titleColumnIndex && isUnreadRow(row)" />
|
|
285
285
|
<CnLockIndicator
|
|
286
|
-
v-if="colIndex ===
|
|
286
|
+
v-if="colIndex === titleColumnIndex"
|
|
287
287
|
:object="row"
|
|
288
288
|
:size="16" />
|
|
289
289
|
<!-- Declared state indicators ride the first data cell
|
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
however the record is shaped. Past the cap the rest move
|
|
294
294
|
into the row menu, because a row of nine icons is not a
|
|
295
295
|
row anyone can triage at a glance. -->
|
|
296
|
-
<span v-if="colIndex ===
|
|
296
|
+
<span v-if="colIndex === titleColumnIndex && indicatorsFor(row).shown.length > 0"
|
|
297
297
|
class="cn-table-row-indicators"
|
|
298
298
|
data-testid="cn-row-indicators">
|
|
299
299
|
<span v-for="indicator in indicatorsFor(row).shown"
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
file names it (OpenRegister `@self.matchedFile`): plain text,
|
|
339
339
|
file name only, under the first cell. -->
|
|
340
340
|
<span
|
|
341
|
-
v-if="colIndex ===
|
|
341
|
+
v-if="colIndex === titleColumnIndex && matchedFileOf(row)"
|
|
342
342
|
class="cn-table-cell__secondary cn-table-cell__matched-file"
|
|
343
343
|
data-testid="cn-row-matched-file">{{ matchedFileLabel(row) }}</span>
|
|
344
344
|
</td>
|
|
@@ -532,7 +532,10 @@ export default {
|
|
|
532
532
|
* formats through `fillTemplate`), or a function of the row. Empty fields leave their placeholder blank; a line that
|
|
533
533
|
* resolves to nothing is not drawn.
|
|
534
534
|
*
|
|
535
|
-
*
|
|
535
|
+
* `link: true` makes that column the one that names the row: it carries
|
|
536
|
+
* the row link and the title look. Without it the first column does.
|
|
537
|
+
*
|
|
538
|
+
* @type {Array<{key: string, label: string, description: string, sortable: boolean, width: string, class: string, cellClass: string, link: boolean, secondary: (string|((row: object) => string))}|string>}
|
|
536
539
|
*/
|
|
537
540
|
columns: {
|
|
538
541
|
type: Array,
|
|
@@ -742,6 +745,17 @@ export default {
|
|
|
742
745
|
default: '',
|
|
743
746
|
},
|
|
744
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Draw an unread record (`@self.unread`) in bold with the unread dot.
|
|
750
|
+
* Off, every row reads alike whatever its read state.
|
|
751
|
+
*
|
|
752
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-unread-rows-are-marked-only-where-the-reader-asked-for-them
|
|
753
|
+
*/
|
|
754
|
+
showUnread: {
|
|
755
|
+
type: Boolean,
|
|
756
|
+
default: true,
|
|
757
|
+
},
|
|
758
|
+
|
|
745
759
|
/**
|
|
746
760
|
* How the board look draws the title column: `link` (the default) is
|
|
747
761
|
* the 15px weight 600 underlined title of the DqZaken board; `plain` is
|
|
@@ -1045,6 +1059,20 @@ export default {
|
|
|
1045
1059
|
* @return {string|null} The column key, or null when not fitting.
|
|
1046
1060
|
* @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view
|
|
1047
1061
|
*/
|
|
1062
|
+
/**
|
|
1063
|
+
* The column that names the row: it carries the row link, the title
|
|
1064
|
+
* look and the unread, lock and indicator marks. The first column with
|
|
1065
|
+
* `link: true`, else the first column (DqTeamwachtrij puts the waiting
|
|
1066
|
+
* time first and links the case title in the second).
|
|
1067
|
+
*
|
|
1068
|
+
* @return {number} The index in `effectiveColumns`.
|
|
1069
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-a-column-can-carry-the-row-link
|
|
1070
|
+
*/
|
|
1071
|
+
titleColumnIndex() {
|
|
1072
|
+
const index = this.effectiveColumns.findIndex((c) => c && c.link === true)
|
|
1073
|
+
return index === -1 ? 0 : index
|
|
1074
|
+
},
|
|
1075
|
+
|
|
1048
1076
|
growColumnKey() {
|
|
1049
1077
|
if (!this.fitWidth || this.effectiveColumns.length === 0) {
|
|
1050
1078
|
return null
|
|
@@ -1365,13 +1393,14 @@ export default {
|
|
|
1365
1393
|
|
|
1366
1394
|
/**
|
|
1367
1395
|
* Whether a row's record changed since the user last looked
|
|
1368
|
-
* (`@self.unread`). A row without the marker
|
|
1396
|
+
* (`@self.unread`), when `showUnread` is on. A row without the marker
|
|
1397
|
+
* renders as it always did.
|
|
1369
1398
|
*
|
|
1370
1399
|
* @param {object} row The row.
|
|
1371
1400
|
* @return {boolean} True when the row is unread.
|
|
1372
1401
|
*/
|
|
1373
1402
|
isUnreadRow(row) {
|
|
1374
|
-
return !!(row && row['@self'] && row['@self'].unread === true)
|
|
1403
|
+
return this.showUnread && !!(row && row['@self'] && row['@self'].unread === true)
|
|
1375
1404
|
},
|
|
1376
1405
|
|
|
1377
1406
|
/**
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
:title="title"
|
|
22
22
|
:description="headerDescription"
|
|
23
23
|
:icon="showTitleIcon && !isBoardLook ? resolvedIcon : ''"
|
|
24
|
+
:breadcrumb="breadcrumb"
|
|
25
|
+
:meta="headerMetaText"
|
|
24
26
|
:visuallyHidden="!showTitle">
|
|
25
27
|
<!-- The board's header buttons (`headerButtons`), beside the
|
|
26
28
|
title. Declaring them takes the Views and Actions menus
|
|
@@ -734,6 +736,7 @@
|
|
|
734
736
|
:columnOverrides="columnOverrides"
|
|
735
737
|
:rowClass="rowClass"
|
|
736
738
|
:rowTitle="rowTitle"
|
|
739
|
+
:showUnread="showUnreadRows"
|
|
737
740
|
:filterable="tableHeaderFilters"
|
|
738
741
|
:activeFilters="effectiveActiveFilters"
|
|
739
742
|
:filterRegister="typeof register === 'string' ? register : ''"
|
|
@@ -1609,6 +1612,31 @@ export default {
|
|
|
1609
1612
|
required: true,
|
|
1610
1613
|
},
|
|
1611
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* A breadcrumb under the title (manifest `config.breadcrumb`): one
|
|
1617
|
+
* crumb `{ label, route?, params?, href?, icon?, separator? }` or a
|
|
1618
|
+
* list; the title follows as the current crumb. Drawn as the detail
|
|
1619
|
+
* header's breadcrumb row. Null (the default) draws none.
|
|
1620
|
+
*
|
|
1621
|
+
* @type {object|Array<object>|null}
|
|
1622
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
1623
|
+
*/
|
|
1624
|
+
breadcrumb: {
|
|
1625
|
+
type: [Object, Array],
|
|
1626
|
+
default: null,
|
|
1627
|
+
},
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* A note after the breadcrumb and a middle dot (manifest `config.headerMeta`),
|
|
1631
|
+
* such as "{total} open": `{total}` and `{shown}` are the list's counts.
|
|
1632
|
+
*
|
|
1633
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
1634
|
+
*/
|
|
1635
|
+
headerMeta: {
|
|
1636
|
+
type: String,
|
|
1637
|
+
default: '',
|
|
1638
|
+
},
|
|
1639
|
+
|
|
1612
1640
|
/** Optional description shown below the title */
|
|
1613
1641
|
description: {
|
|
1614
1642
|
type: String,
|
|
@@ -2232,7 +2260,7 @@ export default {
|
|
|
2232
2260
|
* can open is an ordinary segment; one it cannot (no board columns, no
|
|
2233
2261
|
* map configured) is drawn disabled, named "<mode>: not available on
|
|
2234
2262
|
* this list". Fed from `pages[].config.viewSwitch`. Ignored without the
|
|
2235
|
-
* board look
|
|
2263
|
+
* board look; absent, the board look draws all four.
|
|
2236
2264
|
*
|
|
2237
2265
|
* @type {Array<'table' | 'cards' | 'board' | 'map'>}
|
|
2238
2266
|
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
@@ -3555,6 +3583,8 @@ export default {
|
|
|
3555
3583
|
data() {
|
|
3556
3584
|
return {
|
|
3557
3585
|
currentViewMode: this.viewMode,
|
|
3586
|
+
/** Whether `?_view=` names a mode the page did not offer yet, to open once it does. */
|
|
3587
|
+
routeViewPending: false,
|
|
3558
3588
|
/** The column field the reader picked in the board's column switch; '' = the default. */
|
|
3559
3589
|
boardColumnFieldChoice: '',
|
|
3560
3590
|
/** The rows a reference column field is built from, by field. */
|
|
@@ -4118,11 +4148,14 @@ export default {
|
|
|
4118
4148
|
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
4119
4149
|
*/
|
|
4120
4150
|
viewSwitchDisabledModes() {
|
|
4121
|
-
if (!this.isBoardLook
|
|
4151
|
+
if (!this.isBoardLook) {
|
|
4122
4152
|
return []
|
|
4123
4153
|
}
|
|
4154
|
+
// The index boards draw all four segments, so the board look does
|
|
4155
|
+
// too unless the manifest names its own list.
|
|
4156
|
+
const listed = Array.isArray(this.viewSwitch) ? this.viewSwitch : ['table', 'cards', 'board', 'map']
|
|
4124
4157
|
const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
4125
|
-
return
|
|
4158
|
+
return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
|
|
4126
4159
|
},
|
|
4127
4160
|
|
|
4128
4161
|
/**
|
|
@@ -4160,6 +4193,17 @@ export default {
|
|
|
4160
4193
|
})
|
|
4161
4194
|
},
|
|
4162
4195
|
|
|
4196
|
+
/**
|
|
4197
|
+
* The modes `?_view=` can open: the toggle's modes plus the map when
|
|
4198
|
+
* the page offers it.
|
|
4199
|
+
*
|
|
4200
|
+
* @return {Array<string>} The modes.
|
|
4201
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
|
|
4202
|
+
*/
|
|
4203
|
+
routeViewModes() {
|
|
4204
|
+
return [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
4205
|
+
},
|
|
4206
|
+
|
|
4163
4207
|
/**
|
|
4164
4208
|
* The column fields the board offers (`board.columnFields`), for the
|
|
4165
4209
|
* column switch (screens-board-reference-columns).
|
|
@@ -5380,6 +5424,21 @@ export default {
|
|
|
5380
5424
|
return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
|
|
5381
5425
|
},
|
|
5382
5426
|
|
|
5427
|
+
/**
|
|
5428
|
+
* Whether the table marks unread rows. The boards draw every row alike,
|
|
5429
|
+
* so under the board look only a page that offers the Unread lens
|
|
5430
|
+
* (`personalLenses`) marks them; the Nextcloud look always does.
|
|
5431
|
+
*
|
|
5432
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-unread-rows-are-marked-only-where-the-reader-asked-for-them
|
|
5433
|
+
* @return {boolean} True when unread rows read in bold with the dot.
|
|
5434
|
+
*/
|
|
5435
|
+
showUnreadRows() {
|
|
5436
|
+
if (!this.isBoardLook) {
|
|
5437
|
+
return true
|
|
5438
|
+
}
|
|
5439
|
+
return Array.isArray(this.personalLenses) && this.personalLenses.includes('unread')
|
|
5440
|
+
},
|
|
5441
|
+
|
|
5383
5442
|
/** Resolved schema OBJECT (for column generation / icons / labels). */
|
|
5384
5443
|
/**
|
|
5385
5444
|
* Whether the table shows its header filters. A self-fetching page
|
|
@@ -6002,8 +6061,9 @@ export default {
|
|
|
6002
6061
|
}
|
|
6003
6062
|
// The board's header: Export reads "Download" with its icon and
|
|
6004
6063
|
// the Actions menu reads "Actions", without the manifest saying so.
|
|
6005
|
-
|
|
6006
|
-
|
|
6064
|
+
// A labelled Export keeps the icon too, unless it names its own.
|
|
6065
|
+
if (this.isBoardLook && button.action === 'export') {
|
|
6066
|
+
label = label || t('nextcloud-vue', 'Download')
|
|
6007
6067
|
icon = icon || 'Download'
|
|
6008
6068
|
}
|
|
6009
6069
|
if (label === '' && button.action === 'actions-menu') {
|
|
@@ -6286,6 +6346,22 @@ export default {
|
|
|
6286
6346
|
* @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-the-count-reads-in-the-users-language
|
|
6287
6347
|
* @return {string}
|
|
6288
6348
|
*/
|
|
6349
|
+
/**
|
|
6350
|
+
* The header meta note with the list's counts filled in.
|
|
6351
|
+
*
|
|
6352
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
6353
|
+
* @return {string}
|
|
6354
|
+
*/
|
|
6355
|
+
headerMetaText() {
|
|
6356
|
+
if (!this.headerMeta) {
|
|
6357
|
+
return ''
|
|
6358
|
+
}
|
|
6359
|
+
const total = this.effectivePagination?.total
|
|
6360
|
+
return this.cnTranslate(this.headerMeta)
|
|
6361
|
+
.replace('{shown}', String(this.effectiveObjects.length))
|
|
6362
|
+
.replace('{total}', typeof total === 'number' ? String(total) : '')
|
|
6363
|
+
},
|
|
6364
|
+
|
|
6289
6365
|
headerDescription() {
|
|
6290
6366
|
const total = this.effectivePagination?.total
|
|
6291
6367
|
// Board look: the count line is a template over the rows on this page
|
|
@@ -6476,6 +6552,12 @@ export default {
|
|
|
6476
6552
|
this.applyViewModeFromRoute()
|
|
6477
6553
|
},
|
|
6478
6554
|
|
|
6555
|
+
routeViewModes() {
|
|
6556
|
+
if (this.routeViewPending) {
|
|
6557
|
+
this.applyViewModeFromRoute()
|
|
6558
|
+
}
|
|
6559
|
+
},
|
|
6560
|
+
|
|
6479
6561
|
boardFetchKey: {
|
|
6480
6562
|
immediate: true,
|
|
6481
6563
|
handler(key) {
|
|
@@ -6908,6 +6990,10 @@ export default {
|
|
|
6908
6990
|
*/
|
|
6909
6991
|
async loadFilterCounts() {
|
|
6910
6992
|
const requestKey = this.countRequestKey
|
|
6993
|
+
if (this.isNamedSource) {
|
|
6994
|
+
await this.loadNamedSourceCounts(requestKey)
|
|
6995
|
+
return
|
|
6996
|
+
}
|
|
6911
6997
|
const schema = this.exportSchemaSlug
|
|
6912
6998
|
if (requestKey === '' || !this.register || !schema) {
|
|
6913
6999
|
this.tabCounts = null
|
|
@@ -6939,6 +7025,46 @@ export default {
|
|
|
6939
7025
|
this.savedViewCounts = viewCounts
|
|
6940
7026
|
},
|
|
6941
7027
|
|
|
7028
|
+
/**
|
|
7029
|
+
* The quick-filter counts of a named source (`entitySource`): each tab
|
|
7030
|
+
* that asks is counted by the source's own `count(config)`, with the
|
|
7031
|
+
* tab's filter over `sourceConfig`. A source without `count` shows
|
|
7032
|
+
* none; a failed count leaves that tab without a number.
|
|
7033
|
+
*
|
|
7034
|
+
* @param {string} requestKey The `countRequestKey` this load answers.
|
|
7035
|
+
* @return {Promise<void>}
|
|
7036
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-a-named-source-counts-its-quick-filters
|
|
7037
|
+
*/
|
|
7038
|
+
async loadNamedSourceCounts(requestKey) {
|
|
7039
|
+
const count = this.namedSource ? this.namedSource.count : null
|
|
7040
|
+
if (requestKey === '' || typeof count !== 'function') {
|
|
7041
|
+
this.tabCounts = null
|
|
7042
|
+
this.savedViewCounts = null
|
|
7043
|
+
return
|
|
7044
|
+
}
|
|
7045
|
+
const params = (this.$route && this.$route.params) || {}
|
|
7046
|
+
const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {}
|
|
7047
|
+
const tabCounts = {}
|
|
7048
|
+
await Promise.all(this.countEntries
|
|
7049
|
+
.filter((entry) => entry.key.startsWith('tab:'))
|
|
7050
|
+
.map(async (entry) => {
|
|
7051
|
+
const config = resolveFilterMap({ ...(this.sourceConfig || {}), ...(entry.filter || {}) }, params, ctx)
|
|
7052
|
+
try {
|
|
7053
|
+
const total = await count(config)
|
|
7054
|
+
if (Number.isFinite(total)) {
|
|
7055
|
+
tabCounts[Number(entry.key.slice(4))] = total
|
|
7056
|
+
}
|
|
7057
|
+
} catch {
|
|
7058
|
+
// No number for this tab.
|
|
7059
|
+
}
|
|
7060
|
+
}))
|
|
7061
|
+
if (requestKey !== this.countRequestKey) {
|
|
7062
|
+
return
|
|
7063
|
+
}
|
|
7064
|
+
this.tabCounts = tabCounts
|
|
7065
|
+
this.savedViewCounts = null
|
|
7066
|
+
},
|
|
7067
|
+
|
|
6942
7068
|
// ── Split view (case-page-and-list-as-a-place) ──────────────────
|
|
6943
7069
|
|
|
6944
7070
|
/**
|
|
@@ -9203,6 +9329,7 @@ export default {
|
|
|
9203
9329
|
* @param {string} mode 'table' or 'cards'
|
|
9204
9330
|
*/
|
|
9205
9331
|
onViewModeChange(mode) {
|
|
9332
|
+
this.routeViewPending = false
|
|
9206
9333
|
this.currentViewMode = mode
|
|
9207
9334
|
this.$emit('view-mode-change', mode)
|
|
9208
9335
|
},
|
|
@@ -9212,16 +9339,26 @@ export default {
|
|
|
9212
9339
|
* offers it. The key starts with `_`, like `_search` and `_page`, so it
|
|
9213
9340
|
* is never read as a list filter. A menu entry's `query` can so open a
|
|
9214
9341
|
* list as its board, on the one page that lists the schema
|
|
9215
|
-
* (screens-board-reference-columns).
|
|
9342
|
+
* (screens-board-reference-columns). A mode the page offers only later
|
|
9343
|
+
* (the map, once rows with a geometry are in) opens when it arrives,
|
|
9344
|
+
* unless the reader picked a view first.
|
|
9216
9345
|
*
|
|
9217
9346
|
* @return {void}
|
|
9218
9347
|
* @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-the-address-can-open-a-list-in-a-view-mode
|
|
9348
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
|
|
9219
9349
|
*/
|
|
9220
9350
|
applyViewModeFromRoute() {
|
|
9221
9351
|
const wanted = this.$route?.query?._view
|
|
9222
|
-
if (typeof wanted
|
|
9352
|
+
if (typeof wanted !== 'string' || wanted === '' || wanted === this.currentViewMode) {
|
|
9353
|
+
this.routeViewPending = false
|
|
9354
|
+
return
|
|
9355
|
+
}
|
|
9356
|
+
if (this.routeViewModes.includes(wanted)) {
|
|
9223
9357
|
this.currentViewMode = wanted
|
|
9358
|
+
this.routeViewPending = false
|
|
9359
|
+
return
|
|
9224
9360
|
}
|
|
9361
|
+
this.routeViewPending = true
|
|
9225
9362
|
},
|
|
9226
9363
|
|
|
9227
9364
|
/**
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div
|
|
7
|
+
v-if="crumbs.length > 0 || meta"
|
|
8
|
+
class="cn-header-trail cn-detail-page__header-row2"
|
|
9
|
+
data-testid="cn-header-trail">
|
|
10
|
+
<CnBreadcrumbs
|
|
11
|
+
v-if="crumbs.length > 0"
|
|
12
|
+
class="cn-detail-page__breadcrumbs cn-detail-page__breadcrumbs--row2"
|
|
13
|
+
:crumbs="crumbs"
|
|
14
|
+
:rootText="crumbs[0].icon === undefined"
|
|
15
|
+
:separator="separator"
|
|
16
|
+
data-testid="cn-header-trail-breadcrumbs" />
|
|
17
|
+
<span
|
|
18
|
+
v-if="crumbs.length > 0 && meta"
|
|
19
|
+
class="cn-detail-page__header-dot"
|
|
20
|
+
aria-hidden="true">·</span>
|
|
21
|
+
<span
|
|
22
|
+
v-if="meta"
|
|
23
|
+
class="cn-detail-page__header-meta"
|
|
24
|
+
data-testid="cn-header-trail-meta">{{ meta }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import { CnBreadcrumbs } from '../CnBreadcrumbs/index.js'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The breadcrumb and meta line under a page title: "Instellingen / Zaaktypen
|
|
33
|
+
* · 12 zaaktypen". It draws the same markup and classes as the board look's
|
|
34
|
+
* detail header row, so a list, dashboard or settings page carries the
|
|
35
|
+
* detail page's breadcrumb style rather than one of its own.
|
|
36
|
+
*
|
|
37
|
+
* `breadcrumb` is one crumb or a list, each `{ label, route?, params?, href?,
|
|
38
|
+
* icon?, separator? }`. The page title follows as the current crumb.
|
|
39
|
+
*/
|
|
40
|
+
export default {
|
|
41
|
+
name: 'CnHeaderTrail',
|
|
42
|
+
|
|
43
|
+
components: { CnBreadcrumbs },
|
|
44
|
+
|
|
45
|
+
inject: {
|
|
46
|
+
cnTranslate: { default: () => (key) => key },
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
props: {
|
|
50
|
+
/**
|
|
51
|
+
* The crumbs before the current page, one object or a list.
|
|
52
|
+
*
|
|
53
|
+
* @type {object|Array<object>|null}
|
|
54
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
55
|
+
*/
|
|
56
|
+
breadcrumb: {
|
|
57
|
+
type: [Object, Array],
|
|
58
|
+
default: null,
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** The current crumb: the page title, already translated. */
|
|
62
|
+
current: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: '',
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The note after the crumbs and a middle dot, already resolved.
|
|
69
|
+
*
|
|
70
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
71
|
+
*/
|
|
72
|
+
meta: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: '',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Translate function for the crumb labels; falls back to the injected
|
|
79
|
+
* `cnTranslate`.
|
|
80
|
+
*
|
|
81
|
+
* @type {((key: string) => string)|null}
|
|
82
|
+
*/
|
|
83
|
+
translate: {
|
|
84
|
+
type: Function,
|
|
85
|
+
default: null,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
computed: {
|
|
90
|
+
/**
|
|
91
|
+
* The declared crumbs that carry a label.
|
|
92
|
+
*
|
|
93
|
+
* @return {Array<object>}
|
|
94
|
+
*/
|
|
95
|
+
declared() {
|
|
96
|
+
const list = Array.isArray(this.breadcrumb) ? this.breadcrumb : (this.breadcrumb ? [this.breadcrumb] : [])
|
|
97
|
+
return list.filter((crumb) => crumb && typeof crumb.label === 'string' && crumb.label !== '')
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The crumbs for CnBreadcrumbs: each declared crumb as a link, then the
|
|
102
|
+
* current page. Empty without a declared crumb.
|
|
103
|
+
*
|
|
104
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
105
|
+
* @return {Array<object>}
|
|
106
|
+
*/
|
|
107
|
+
crumbs() {
|
|
108
|
+
if (this.declared.length === 0) {
|
|
109
|
+
return []
|
|
110
|
+
}
|
|
111
|
+
const out = this.declared.map((crumb) => {
|
|
112
|
+
const entry = { label: (this.translate ?? this.cnTranslate)(crumb.label) }
|
|
113
|
+
if (typeof crumb.icon === 'string' && crumb.icon !== '') {
|
|
114
|
+
entry.icon = crumb.icon
|
|
115
|
+
}
|
|
116
|
+
if (crumb.route) {
|
|
117
|
+
entry.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) }
|
|
118
|
+
} else if (crumb.href) {
|
|
119
|
+
entry.href = crumb.href
|
|
120
|
+
}
|
|
121
|
+
return entry
|
|
122
|
+
})
|
|
123
|
+
if (this.current) {
|
|
124
|
+
out.push({ label: this.current })
|
|
125
|
+
}
|
|
126
|
+
return out
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The text between the crumbs: the first crumb's `separator`, else `/`.
|
|
131
|
+
*
|
|
132
|
+
* @return {string}
|
|
133
|
+
*/
|
|
134
|
+
separator() {
|
|
135
|
+
const declared = this.declared[0] && this.declared[0].separator
|
|
136
|
+
return typeof declared === 'string' && declared !== '' ? declared : '/'
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
@@ -81,3 +81,14 @@ With extra content (e.g., a badge or status indicator):
|
|
|
81
81
|
</template>
|
|
82
82
|
</CnPageHeader>
|
|
83
83
|
```
|
|
84
|
+
|
|
85
|
+
Breadcrumb and meta: `breadcrumb` (one crumb `{ label, route?, params?, href?, icon?, separator? }` or a list) draws a
|
|
86
|
+
line under the title in the detail header's breadcrumb style, with the title as the current crumb; `meta` follows after a
|
|
87
|
+
middle dot:
|
|
88
|
+
|
|
89
|
+
```vue
|
|
90
|
+
<CnPageHeader
|
|
91
|
+
title="Zaaktypen"
|
|
92
|
+
:breadcrumb="{ label: 'Instellingen', href: '#instellingen' }"
|
|
93
|
+
meta="14 zaaktypen" />
|
|
94
|
+
```
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
<h1 class="cn-page-header__title" data-testid="cn-page-title">
|
|
14
14
|
{{ resolvedTitle }}
|
|
15
15
|
</h1>
|
|
16
|
+
<CnHeaderTrail
|
|
17
|
+
v-if="!visuallyHidden"
|
|
18
|
+
:breadcrumb="breadcrumb"
|
|
19
|
+
:current="resolvedTitle"
|
|
20
|
+
:meta="resolvedMeta"
|
|
21
|
+
:translate="effectiveTranslate" />
|
|
16
22
|
<p v-if="description && !visuallyHidden" class="cn-page-header__description" data-testid="cn-page-description">
|
|
17
23
|
{{ resolvedDescription }}
|
|
18
24
|
</p>
|
|
@@ -22,6 +28,7 @@
|
|
|
22
28
|
</template>
|
|
23
29
|
|
|
24
30
|
<script>
|
|
31
|
+
import CnHeaderTrail from './CnHeaderTrail.vue'
|
|
25
32
|
import { CnIcon } from '../CnIcon/index.js'
|
|
26
33
|
|
|
27
34
|
/**
|
|
@@ -64,6 +71,7 @@ export default {
|
|
|
64
71
|
name: 'CnPageHeader',
|
|
65
72
|
|
|
66
73
|
components: {
|
|
74
|
+
CnHeaderTrail,
|
|
67
75
|
CnIcon,
|
|
68
76
|
},
|
|
69
77
|
|
|
@@ -90,6 +98,31 @@ export default {
|
|
|
90
98
|
default: '',
|
|
91
99
|
},
|
|
92
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A breadcrumb under the title: one crumb or a list, each
|
|
103
|
+
* `{ label, route?, params?, href?, icon?, separator? }`. The title
|
|
104
|
+
* follows as the current crumb. Drawn as the board look's detail
|
|
105
|
+
* header row.
|
|
106
|
+
*
|
|
107
|
+
* @type {object|Array<object>|null}
|
|
108
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
109
|
+
*/
|
|
110
|
+
breadcrumb: {
|
|
111
|
+
type: [Object, Array],
|
|
112
|
+
default: null,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A note after the breadcrumb and a middle dot, such as "12 open". Run
|
|
117
|
+
* through the host translate function.
|
|
118
|
+
*
|
|
119
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
120
|
+
*/
|
|
121
|
+
meta: {
|
|
122
|
+
type: String,
|
|
123
|
+
default: '',
|
|
124
|
+
},
|
|
125
|
+
|
|
93
126
|
/** Optional MDI icon name (rendered via CnIcon) */
|
|
94
127
|
icon: {
|
|
95
128
|
type: String,
|
|
@@ -153,6 +186,15 @@ export default {
|
|
|
153
186
|
resolvedDescription() {
|
|
154
187
|
return this.description ? this.effectiveTranslate(this.description) : this.description
|
|
155
188
|
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The meta note run through the host translate function.
|
|
192
|
+
*
|
|
193
|
+
* @return {string}
|
|
194
|
+
*/
|
|
195
|
+
resolvedMeta() {
|
|
196
|
+
return this.meta ? this.effectiveTranslate(this.meta) : ''
|
|
197
|
+
},
|
|
156
198
|
},
|
|
157
199
|
}
|
|
158
200
|
</script>
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
Board variant (`variant: "board"`): the footer of the table card as the
|
|
9
9
|
screens draw it. The count text at the start, numbered 34px links at
|
|
10
10
|
the end, "Previous" only after the first page and "Next" only before
|
|
11
|
-
the last. No First, no Last, no page-size select.
|
|
11
|
+
the last (inert on a single page). No First, no Last, no page-size select.
|
|
12
12
|
-->
|
|
13
13
|
<template v-if="isBoardVariant">
|
|
14
14
|
<span class="cn-pagination__page-info cn-pagination__board-count" data-testid="cn-pagination-board-count">
|
|
15
15
|
{{ boardCountText }}
|
|
16
16
|
</span>
|
|
17
|
-
<nav
|
|
17
|
+
<nav class="cn-pagination__board-pages" :aria-label="t('nextcloud-vue', 'Pagination')">
|
|
18
18
|
<button
|
|
19
19
|
v-if="currentPage > 1"
|
|
20
20
|
type="button"
|
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
@click="changePage(currentPage + 1)">
|
|
44
44
|
{{ nextLabel }}
|
|
45
45
|
</button>
|
|
46
|
+
<!-- A single page still draws its "1" and an inert Next, as the boards do. -->
|
|
47
|
+
<span
|
|
48
|
+
v-else-if="totalPages <= 1"
|
|
49
|
+
class="cn-pagination__board-link cn-pagination__board-link--wide cn-pagination__board-link--disabled"
|
|
50
|
+
aria-disabled="true"
|
|
51
|
+
data-testid="cn-pagination-next">
|
|
52
|
+
{{ nextLabel }}
|
|
53
|
+
</span>
|
|
46
54
|
</nav>
|
|
47
55
|
</template>
|
|
48
56
|
|
|
@@ -727,9 +727,9 @@ export default {
|
|
|
727
727
|
},
|
|
728
728
|
|
|
729
729
|
/**
|
|
730
|
-
* The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, emptyText? }`):
|
|
731
|
-
* "3 dagen te laat" in the danger tone, "Vandaag", the date
|
|
732
|
-
* `emptyText` when the field is empty.
|
|
730
|
+
* The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, upcomingLabel?, upcomingWeeksLabel?, upcomingWeeksFrom?, emptyText? }`):
|
|
731
|
+
* "3 dagen te laat" in the danger tone, "Vandaag", the date or the time
|
|
732
|
+
* left ("nog 4 dagen") otherwise, `emptyText` when the field is empty.
|
|
733
733
|
*
|
|
734
734
|
* @param {object} row The row.
|
|
735
735
|
* @return {{text: string, tone: string}|null} The text and tone, or null.
|