@conduction/nextcloud-vue 2.78.0 → 2.79.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/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +80 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +115 -91
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +25 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +61 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +106 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +314 -15
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2039 -581
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +314 -15
- package/l10n/en.json +61 -1
- package/l10n/nl.json +106 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +119 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +81 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +27 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/useAppManifest.js +8 -3
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +92 -14
- package/src/css/look-board.css +77 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +46 -1
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
<!-- @slot title-meta Content on the title's row, right of the title (e.g. a page-wide picker). Fill it from a manifest with `page.slots: { "title-meta": "<RegistryName>" }`. -->
|
|
34
34
|
<slot name="title-meta" />
|
|
35
35
|
</div>
|
|
36
|
-
<p v-if="
|
|
37
|
-
{{
|
|
36
|
+
<p v-if="subtitleText" class="cn-dashboard-page__description" data-testid="cn-dashboard-page-subtitle">
|
|
37
|
+
{{ subtitleText }}
|
|
38
38
|
</p>
|
|
39
39
|
</div>
|
|
40
40
|
<div class="cn-dashboard-page__header-actions">
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
widget on the page draws it. Its options control the view
|
|
43
43
|
region below the grid. -->
|
|
44
44
|
<CnSegmentedControl
|
|
45
|
-
v-if="showsPageViewSwitch"
|
|
45
|
+
v-if="showsPageViewSwitch && !switchInRow"
|
|
46
46
|
class="cn-page-view-switch"
|
|
47
47
|
:options="viewSwitchOptions"
|
|
48
48
|
:modelValue="activeViewId"
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
Separate from the per-widget menus. `docs-anchor` deep-links
|
|
98
98
|
the docs item to THIS page's section. -->
|
|
99
99
|
<CnActionsMenu
|
|
100
|
+
v-if="showActionsMenu"
|
|
100
101
|
:showRefresh="showRefresh"
|
|
101
102
|
:showRequestFeature="showRequestFeature"
|
|
102
103
|
:showReportBug="showReportBug"
|
|
@@ -138,6 +139,39 @@
|
|
|
138
139
|
</div>
|
|
139
140
|
</div>
|
|
140
141
|
|
|
142
|
+
<!-- The switch row (screens-dashboard-greeting-header): in the board
|
|
143
|
+
look the page's view switch sits under the header, left, and the
|
|
144
|
+
page's `viewLinks` sit right of it as small pills (DqMijnWerk). -->
|
|
145
|
+
<div
|
|
146
|
+
v-if="showHeader && (switchInRow || resolvedViewLinks.length > 0)"
|
|
147
|
+
class="cn-dashboard-page__switch-row"
|
|
148
|
+
data-testid="cn-dashboard-page-switch-row">
|
|
149
|
+
<CnSegmentedControl
|
|
150
|
+
v-if="switchInRow"
|
|
151
|
+
class="cn-page-view-switch"
|
|
152
|
+
:options="viewSwitchOptions"
|
|
153
|
+
:modelValue="activeViewId"
|
|
154
|
+
:ariaLabel="viewSwitchLabel"
|
|
155
|
+
:controls="viewRegionId"
|
|
156
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
157
|
+
@update:modelValue="selectView" />
|
|
158
|
+
<span class="cn-dashboard-page__switch-row-spacer" />
|
|
159
|
+
<component
|
|
160
|
+
:is="link.tag"
|
|
161
|
+
v-for="link in resolvedViewLinks"
|
|
162
|
+
:key="link.key"
|
|
163
|
+
v-bind="link.attrs"
|
|
164
|
+
class="cn-dashboard-page__view-link"
|
|
165
|
+
data-testid="cn-dashboard-page-view-link">
|
|
166
|
+
<CnWidgetIcon
|
|
167
|
+
v-if="link.icon"
|
|
168
|
+
:name="link.icon"
|
|
169
|
+
:size="14"
|
|
170
|
+
class="cn-dashboard-page__view-link-icon" />
|
|
171
|
+
{{ link.label }}
|
|
172
|
+
</component>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
141
175
|
<!-- Date-range header (optional).
|
|
142
176
|
Rendered only when `dateRange.enabled === true` AND
|
|
143
177
|
`dateRange.showHeaderPicker !== false`. The range STATE
|
|
@@ -868,7 +902,8 @@
|
|
|
868
902
|
</template>
|
|
869
903
|
|
|
870
904
|
<script>
|
|
871
|
-
import {
|
|
905
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
906
|
+
import { getCanonicalLocale, translate as t } from '@nextcloud/l10n'
|
|
872
907
|
import { NcActionButton, NcActionInput, NcActions, NcActionSeparator, NcButton, NcLoadingIcon, NcSelect } from '@nextcloud/vue'
|
|
873
908
|
import { provide, ref, watch } from 'vue'
|
|
874
909
|
import CalendarRange from 'vue-material-design-icons/CalendarRange.vue'
|
|
@@ -889,6 +924,7 @@ import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
|
|
|
889
924
|
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
890
925
|
import CnStatsBlockWidget from '../CnStatsBlockWidget/CnStatsBlockWidget.vue'
|
|
891
926
|
import CnTileWidget from '../CnTileWidget/CnTileWidget.vue'
|
|
927
|
+
import CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'
|
|
892
928
|
import CnWidgetRefItem from '../CnWidgetRefItem/CnWidgetRefItem.vue'
|
|
893
929
|
import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
894
930
|
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
@@ -1056,6 +1092,7 @@ export default {
|
|
|
1056
1092
|
CnLeafMountHost,
|
|
1057
1093
|
CnSegmentedControl,
|
|
1058
1094
|
CnKpiGrid,
|
|
1095
|
+
CnWidgetIcon,
|
|
1059
1096
|
},
|
|
1060
1097
|
|
|
1061
1098
|
mixins: [pageViews],
|
|
@@ -1156,6 +1193,50 @@ export default {
|
|
|
1156
1193
|
default: () => [],
|
|
1157
1194
|
},
|
|
1158
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* Greet the reader in the header subtitle (manifest `config.greeting`):
|
|
1198
|
+
* `true` (or `"first"`) writes "Good afternoon, Pieter · Monday 5
|
|
1199
|
+
* October 2026", `"full"` uses the whole display name. The page
|
|
1200
|
+
* `description`, when set, follows after another middle dot. Off by
|
|
1201
|
+
* default: the subtitle is the description alone, as before.
|
|
1202
|
+
*
|
|
1203
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1204
|
+
* @type {boolean|string}
|
|
1205
|
+
*/
|
|
1206
|
+
greeting: {
|
|
1207
|
+
type: [Boolean, String],
|
|
1208
|
+
default: false,
|
|
1209
|
+
},
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Links drawn as small pills on the switch row under the header
|
|
1213
|
+
* (manifest `config.viewLinks`), as DqMijnWerk's "Uw wachtrij / Aan
|
|
1214
|
+
* mij toegewezen / Dag afsluiten". Each `{ label, icon?, route?,
|
|
1215
|
+
* href? }`: `route` is a route name or a `{ name, params?, query? }`
|
|
1216
|
+
* location, `href` a plain URL; `label` goes through the host
|
|
1217
|
+
* translate function. An entry without a label or a target is skipped.
|
|
1218
|
+
*
|
|
1219
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
1220
|
+
* @type {Array<object>}
|
|
1221
|
+
*/
|
|
1222
|
+
viewLinks: {
|
|
1223
|
+
type: Array,
|
|
1224
|
+
default: () => [],
|
|
1225
|
+
},
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Whether the header draws the page Actions menu (manifest
|
|
1229
|
+
* `config.showActionsMenu`). `false` removes it, for a board page
|
|
1230
|
+
* whose header carries only the buildiq square and the primary.
|
|
1231
|
+
*
|
|
1232
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-can-drop-the-page-actions-menu
|
|
1233
|
+
* @type {boolean}
|
|
1234
|
+
*/
|
|
1235
|
+
showActionsMenu: {
|
|
1236
|
+
type: Boolean,
|
|
1237
|
+
default: true,
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1159
1240
|
/**
|
|
1160
1241
|
* Whether a widget draws its overflow Actions menu when neither its
|
|
1161
1242
|
* placement nor its definition says (`showActions`). `false` (manifest
|
|
@@ -1848,6 +1929,97 @@ export default {
|
|
|
1848
1929
|
return typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
|
|
1849
1930
|
},
|
|
1850
1931
|
|
|
1932
|
+
/**
|
|
1933
|
+
* The greeting for the time of day, with the reader's first (or
|
|
1934
|
+
* full) name when known, or '' when `greeting` is off.
|
|
1935
|
+
*
|
|
1936
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1937
|
+
* @return {string}
|
|
1938
|
+
*/
|
|
1939
|
+
greetingLine() {
|
|
1940
|
+
if (this.greeting !== true && this.greeting !== 'first' && this.greeting !== 'full') {
|
|
1941
|
+
return ''
|
|
1942
|
+
}
|
|
1943
|
+
let user
|
|
1944
|
+
try {
|
|
1945
|
+
user = getCurrentUser()
|
|
1946
|
+
} catch {
|
|
1947
|
+
user = null
|
|
1948
|
+
}
|
|
1949
|
+
const displayName = (user && typeof user.displayName === 'string') ? user.displayName.trim() : ''
|
|
1950
|
+
const name = this.greeting === 'full' ? displayName : displayName.split(/\s+/)[0]
|
|
1951
|
+
const hour = new Date().getHours()
|
|
1952
|
+
if (hour < 12) {
|
|
1953
|
+
return name ? t('nextcloud-vue', 'Good morning, {name}', { name }) : t('nextcloud-vue', 'Good morning')
|
|
1954
|
+
}
|
|
1955
|
+
if (hour < 18) {
|
|
1956
|
+
return name ? t('nextcloud-vue', 'Good afternoon, {name}', { name }) : t('nextcloud-vue', 'Good afternoon')
|
|
1957
|
+
}
|
|
1958
|
+
return name ? t('nextcloud-vue', 'Good evening, {name}', { name }) : t('nextcloud-vue', 'Good evening')
|
|
1959
|
+
},
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* The header subtitle: with `greeting` on, the greeting, today's date
|
|
1963
|
+
* written out and the description, joined by a middle dot; else the
|
|
1964
|
+
* description alone, as before.
|
|
1965
|
+
*
|
|
1966
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1967
|
+
* @return {string}
|
|
1968
|
+
*/
|
|
1969
|
+
subtitleText() {
|
|
1970
|
+
const description = this.description ? this.resolvedDescription : ''
|
|
1971
|
+
const greeting = this.greetingLine
|
|
1972
|
+
if (greeting === '') {
|
|
1973
|
+
return description
|
|
1974
|
+
}
|
|
1975
|
+
const options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }
|
|
1976
|
+
let date
|
|
1977
|
+
try {
|
|
1978
|
+
date = new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(new Date())
|
|
1979
|
+
} catch {
|
|
1980
|
+
date = new Intl.DateTimeFormat(undefined, options).format(new Date())
|
|
1981
|
+
}
|
|
1982
|
+
return [greeting, date, description].filter((part) => part !== '').join(' · ')
|
|
1983
|
+
},
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Whether the page's view switch sits on the switch row under the
|
|
1987
|
+
* header (board look) rather than among the header actions.
|
|
1988
|
+
*
|
|
1989
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
1990
|
+
* @return {boolean}
|
|
1991
|
+
*/
|
|
1992
|
+
switchInRow() {
|
|
1993
|
+
return this.isBoardLook && this.showsPageViewSwitch
|
|
1994
|
+
},
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* `viewLinks` as render descriptors: a router link for a route, an
|
|
1998
|
+
* anchor for an href, the label translated. Entries without a label
|
|
1999
|
+
* or a target are dropped.
|
|
2000
|
+
*
|
|
2001
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
2002
|
+
* @return {Array<{key: string, tag: string, attrs: object, label: string, icon: string}>}
|
|
2003
|
+
*/
|
|
2004
|
+
resolvedViewLinks() {
|
|
2005
|
+
const links = Array.isArray(this.viewLinks) ? this.viewLinks : []
|
|
2006
|
+
const out = []
|
|
2007
|
+
links.forEach((link, index) => {
|
|
2008
|
+
if (!link || typeof link.label !== 'string' || link.label === '') {
|
|
2009
|
+
return
|
|
2010
|
+
}
|
|
2011
|
+
const icon = typeof link.icon === 'string' ? link.icon : ''
|
|
2012
|
+
const label = this.effectiveTranslate(link.label)
|
|
2013
|
+
if (link.route && this.$router) {
|
|
2014
|
+
const to = typeof link.route === 'string' ? { name: link.route } : link.route
|
|
2015
|
+
out.push({ key: `route-${index}`, tag: 'router-link', attrs: { to }, label, icon })
|
|
2016
|
+
} else if (typeof link.href === 'string' && link.href !== '') {
|
|
2017
|
+
out.push({ key: `href-${index}`, tag: 'a', attrs: { href: link.href }, label, icon })
|
|
2018
|
+
}
|
|
2019
|
+
})
|
|
2020
|
+
return out
|
|
2021
|
+
},
|
|
2022
|
+
|
|
1851
2023
|
/**
|
|
1852
2024
|
* The page title run through the host translate function so a
|
|
1853
2025
|
* manifest-authored source string localises.
|
|
@@ -4392,6 +4564,49 @@ export default {
|
|
|
4392
4564
|
height: var(--cn-board-control-height, 40px);
|
|
4393
4565
|
}
|
|
4394
4566
|
|
|
4567
|
+
/* The switch row under the header (screens-dashboard-greeting-header): the
|
|
4568
|
+
view switch left, the `viewLinks` pills right (DqMijnWerk: pills 34px high,
|
|
4569
|
+
radius 17, 13px/600, a 14px icon, primary light on primary deep). */
|
|
4570
|
+
.cn-dashboard-page__switch-row {
|
|
4571
|
+
display: flex;
|
|
4572
|
+
flex-wrap: wrap;
|
|
4573
|
+
align-items: center;
|
|
4574
|
+
gap: 10px;
|
|
4575
|
+
margin-bottom: var(--cn-board-section-gap, 20px);
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
.cn-dashboard-page__switch-row-spacer {
|
|
4579
|
+
flex: 1 1 auto;
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
.cn-dashboard-page__view-link {
|
|
4583
|
+
display: inline-flex;
|
|
4584
|
+
align-items: center;
|
|
4585
|
+
gap: 6px;
|
|
4586
|
+
height: 34px;
|
|
4587
|
+
padding: 0 12px;
|
|
4588
|
+
border-radius: 17px;
|
|
4589
|
+
background: var(--color-primary-element-light);
|
|
4590
|
+
color: var(--color-primary-element-light-text);
|
|
4591
|
+
font-size: 13px;
|
|
4592
|
+
font-weight: 600;
|
|
4593
|
+
white-space: nowrap;
|
|
4594
|
+
text-decoration: none !important;
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
.cn-dashboard-page__view-link:hover {
|
|
4598
|
+
background: var(--color-primary-element-light-hover, var(--color-primary-element-light));
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
.cn-dashboard-page__view-link:focus-visible {
|
|
4602
|
+
outline: 2px solid var(--color-main-text);
|
|
4603
|
+
outline-offset: 2px;
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
.cn-dashboard-page__view-link-icon {
|
|
4607
|
+
display: inline-flex;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4395
4610
|
/* The period group, in a row of its own between the header and the KPI row. */
|
|
4396
4611
|
.cn-dashboard-page__date-range--segmented {
|
|
4397
4612
|
margin-bottom: 16px;
|
|
@@ -198,9 +198,11 @@ export default {
|
|
|
198
198
|
| `rowIndicatorCap` | Number | `3` | How many declared indicators render on the row before the rest move into the row menu, reachable as `indicatorsFor(row).overflow`. |
|
|
199
199
|
| `selectAllLabel` | String | `'Select all rows'` | Accessible name (`aria-label`) for the header select-all checkbox, so screen readers announce a named control (WCAG 4.1.2) |
|
|
200
200
|
| `selectRowLabel` | String | `'Select row'` | Accessible name (`aria-label`) for each per-row select checkbox, so screen readers announce a named control (WCAG 4.1.2) |
|
|
201
|
+
| `rowTitle` | String | `'link'` | Board look only: how the title column draws its title. `link` underlines the title (not its secondary line). `plain` draws it as 15px bold text without an underline over a 13px muted secondary line, as the PqTickets board does; the row stays clickable. The Nextcloud look ignores it. |
|
|
201
202
|
| `hideHeader` | Boolean | `false` | Hide the column-header row (`<thead>`) — for compact dashboard list widgets that want a plain bordered-row list without column labels |
|
|
202
203
|
| `pinnedCount` | Number | `0` | How many data columns are pinned to the start of the table. They, and the selection and icon columns, stay in view when the table scrolls sideways |
|
|
203
204
|
| `fixedLayout` | Boolean | `false` | Switch to `table-layout: fixed` so each column's `width` is authoritative rather than a hint the browser may override from cell content. Long unbreakable values (a PHP FQCN, a UUID) then wrap inside their cell instead of widening the column or painting past it, and no single unsized column soaks up all remaining width. Size every column when using it — percentages summing to 100 are easiest to reason about |
|
|
205
|
+
| `fitWidth` | Boolean | `false` | Fit the table to its container instead of growing past it: one column (a column's `grow: true`, else `title`, else `name`, else the first) takes the room that is left and ends in an ellipsis, and every other column keeps its content on one line, so trailing columns stay visible in a narrow tile. `CnWidgetObjectTable` turns it on |
|
|
204
206
|
| `fillHeight` | Boolean | `false` | Fill the parent's height (a flex-column card / widget content area) so an optional `#footer` is pushed to the bottom instead of floating under a short list; the footer stays pinned via its sticky rule when the list overflows. No-op outside a height-constrained parent — opt-in |
|
|
205
207
|
| `filterable` | Boolean | `false` | Show a filter button in every header whose column can filter: enum, boolean, text, number, date and reference columns backed by a schema property. A column opts out with `filterable: false`. Applying a filter emits `column-filter`; the host owns the state and passes it back as `activeFilters`. `CnIndexPage` turns it on by default. |
|
|
206
208
|
| `activeFilters` | Object | `{}` | The active filter map, `{ paramKey: values[] }`: the same map the facet sidebar writes, so a header filter and a sidebar filter on one field show the same state. |
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
data-testid="cn-object-list"
|
|
5
5
|
:class="{
|
|
6
6
|
'cn-table-container--board': isBoardLook,
|
|
7
|
+
'cn-table-container--title-plain': isBoardLook && rowTitle === 'plain',
|
|
7
8
|
'cn-table-container--scrollable': scrollable,
|
|
8
9
|
'cn-table-container--borderless': borderless,
|
|
9
10
|
'cn-table-container--fill': fillHeight,
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
:aria-label="isScrollable ? scrollRegionLabel : undefined">
|
|
97
98
|
<table
|
|
98
99
|
class="cn-data-table"
|
|
99
|
-
:class="{ 'cn-data-table--fixed': fixedLayout }"
|
|
100
|
+
:class="{ 'cn-data-table--fixed': fixedLayout, 'cn-data-table--fit': fitWidth }"
|
|
100
101
|
data-testid="cn-object-list-table">
|
|
101
102
|
<thead v-if="!hideHeader">
|
|
102
103
|
<tr>
|
|
@@ -128,6 +129,7 @@
|
|
|
128
129
|
col.sortable ? 'cn-table-header--sortable' : '',
|
|
129
130
|
col.class || '',
|
|
130
131
|
pinClass(leadingCount + colIndex),
|
|
132
|
+
isGrowColumn(col) ? 'cn-table-col--grow' : '',
|
|
131
133
|
]"
|
|
132
134
|
:style="{ ...(col.width ? { width: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
133
135
|
:aria-sort="ariaSortFor(col)"
|
|
@@ -256,7 +258,7 @@
|
|
|
256
258
|
<td
|
|
257
259
|
v-for="(col, colIndex) in effectiveColumns"
|
|
258
260
|
:key="col.key"
|
|
259
|
-
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex === 0 ? 'cn-table-col--title' : '']"
|
|
261
|
+
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex === 0 ? 'cn-table-col--title' : '', isGrowColumn(col) ? 'cn-table-col--grow' : '']"
|
|
260
262
|
:style="{ ...(col.width ? { maxWidth: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
261
263
|
@mouseenter="titleWhenClipped">
|
|
262
264
|
<!-- A row with a `rowClickRoute` is a real link: this anchor
|
|
@@ -499,6 +501,16 @@ function fillSecondaryTemplate(template, valueOf) {
|
|
|
499
501
|
* </CnDataTable>
|
|
500
502
|
* ```
|
|
501
503
|
*/
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Column keys that name an object's system dates in OpenRegister's `@self`
|
|
507
|
+
* block. A column may declare one as its key (`@self.created`); it renders as
|
|
508
|
+
* a date-time although the schema has no property behind it.
|
|
509
|
+
*
|
|
510
|
+
* @type {string[]}
|
|
511
|
+
*/
|
|
512
|
+
const SYSTEM_DATE_KEYS = ['@self.created', '@self.updated', '@self.published', '@self.depublished']
|
|
513
|
+
|
|
502
514
|
export default {
|
|
503
515
|
name: 'CnDataTable',
|
|
504
516
|
|
|
@@ -761,6 +773,21 @@ export default {
|
|
|
761
773
|
default: '',
|
|
762
774
|
},
|
|
763
775
|
|
|
776
|
+
/**
|
|
777
|
+
* How the board look draws the title column: `link` (the default) is
|
|
778
|
+
* the 15px weight 600 underlined title of the DqZaken board; `plain` is
|
|
779
|
+
* the 15px bold title without an underline and a 13px muted secondary
|
|
780
|
+
* line, as the PqTickets and OcPublicaties boards draw it. The row stays
|
|
781
|
+
* clickable either way. The Nextcloud look ignores it.
|
|
782
|
+
*
|
|
783
|
+
* @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
|
|
784
|
+
*/
|
|
785
|
+
rowTitle: {
|
|
786
|
+
type: String,
|
|
787
|
+
default: 'link',
|
|
788
|
+
validator: (value) => ['link', 'plain'].includes(value),
|
|
789
|
+
},
|
|
790
|
+
|
|
764
791
|
/**
|
|
765
792
|
* Drop the container's card chrome (border, radius, shadow) so the table
|
|
766
793
|
* sits flush inside a parent that already provides a card (e.g. a
|
|
@@ -820,6 +847,23 @@ export default {
|
|
|
820
847
|
default: false,
|
|
821
848
|
},
|
|
822
849
|
|
|
850
|
+
/**
|
|
851
|
+
* Fit the table to its container instead of letting it grow past it.
|
|
852
|
+
* One column takes the room that is left and cuts its text with an
|
|
853
|
+
* ellipsis (the full value stays in the tooltip); every other column
|
|
854
|
+
* keeps its content on one line at its own width, so trailing columns
|
|
855
|
+
* such as a date stay visible in a narrow tile. The column that grows
|
|
856
|
+
* is the first with `grow: true`, else the one keyed `title` or `name`,
|
|
857
|
+
* else the first column. CnWidgetObjectTable turns this on.
|
|
858
|
+
*
|
|
859
|
+
* @type {boolean}
|
|
860
|
+
* @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
|
|
861
|
+
*/
|
|
862
|
+
fitWidth: {
|
|
863
|
+
type: Boolean,
|
|
864
|
+
default: false,
|
|
865
|
+
},
|
|
866
|
+
|
|
823
867
|
/**
|
|
824
868
|
* Max number of rows to display. When the total exceeds it, only the first
|
|
825
869
|
* `limit` render and the "View all" footer appears (with `viewAllRoute`).
|
|
@@ -1025,6 +1069,25 @@ export default {
|
|
|
1025
1069
|
},
|
|
1026
1070
|
|
|
1027
1071
|
/** @return {number} Leading cells before the data columns (selection, icon). */
|
|
1072
|
+
/**
|
|
1073
|
+
* The key of the column that takes the spare width under `fitWidth`:
|
|
1074
|
+
* the first with `grow: true`, else `title` or `name`, else the first.
|
|
1075
|
+
*
|
|
1076
|
+
* @return {string|null} The column key, or null when not fitting.
|
|
1077
|
+
* @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
|
|
1078
|
+
*/
|
|
1079
|
+
growColumnKey() {
|
|
1080
|
+
if (!this.fitWidth || this.effectiveColumns.length === 0) {
|
|
1081
|
+
return null
|
|
1082
|
+
}
|
|
1083
|
+
const cols = this.effectiveColumns
|
|
1084
|
+
const chosen = cols.find((c) => c && c.grow === true)
|
|
1085
|
+
|| cols.find((c) => c && c.key === 'title')
|
|
1086
|
+
|| cols.find((c) => c && c.key === 'name')
|
|
1087
|
+
|| cols[0]
|
|
1088
|
+
return chosen ? chosen.key : null
|
|
1089
|
+
},
|
|
1090
|
+
|
|
1028
1091
|
leadingCount() {
|
|
1029
1092
|
return (this.selectable ? 1 : 0) + (this.rowIcon ? 1 : 0)
|
|
1030
1093
|
},
|
|
@@ -1321,6 +1384,16 @@ export default {
|
|
|
1321
1384
|
},
|
|
1322
1385
|
|
|
1323
1386
|
methods: {
|
|
1387
|
+
/**
|
|
1388
|
+
* Whether a column is the one that takes the spare width (`fitWidth`).
|
|
1389
|
+
*
|
|
1390
|
+
* @param {object} col The column.
|
|
1391
|
+
* @return {boolean}
|
|
1392
|
+
*/
|
|
1393
|
+
isGrowColumn(col) {
|
|
1394
|
+
return this.growColumnKey !== null && !!col && col.key === this.growColumnKey
|
|
1395
|
+
},
|
|
1396
|
+
|
|
1324
1397
|
/**
|
|
1325
1398
|
* Whether a row's record changed since the user last looked
|
|
1326
1399
|
* (`@self.unread`). A row without the marker renders as it always did.
|
|
@@ -1651,11 +1724,17 @@ export default {
|
|
|
1651
1724
|
* the schema property's own `colorMap` / `x-color-map`.
|
|
1652
1725
|
*
|
|
1653
1726
|
* @param {object} col Column definition.
|
|
1727
|
+
* @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-a-system-date-can-be-a-column
|
|
1654
1728
|
* @return {object} Property definition for CnCellRenderer.
|
|
1655
1729
|
* @spec openspec/changes/screens-cell-pill-parity/specs/cell-pill-tones/spec.md#requirement-a-manifest-column-colours-its-enum-pills
|
|
1656
1730
|
*/
|
|
1657
1731
|
columnProperty(col) {
|
|
1658
1732
|
const base = this.getSchemaProperty(col.key)
|
|
1733
|
+
// A system date (`@self.created`, `@self.updated`, ...) has no
|
|
1734
|
+
// schema property behind it; it renders as a date-time.
|
|
1735
|
+
if (Object.keys(base).length === 0 && SYSTEM_DATE_KEYS.includes(col.key) && !col.type && !col.format) {
|
|
1736
|
+
return { type: 'string', format: 'date-time' }
|
|
1737
|
+
}
|
|
1659
1738
|
const colorMap = col && col.colorMap && typeof col.colorMap === 'object' && !Array.isArray(col.colorMap) ? col.colorMap : null
|
|
1660
1739
|
if (col && (col.format || col.type || col.enum || colorMap)) {
|
|
1661
1740
|
return {
|
|
@@ -244,7 +244,9 @@ import CnObjectFilesWidget from '../CnObjectFilesWidget/CnObjectFilesWidget.vue'
|
|
|
244
244
|
import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
245
245
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
246
246
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
247
|
+
import { normalizeLook } from '../../composables/useLook.js'
|
|
247
248
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
249
|
+
import { isActivityWidget } from '../../utils/headerMeta.js'
|
|
248
250
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
249
251
|
import {
|
|
250
252
|
isCardWidgetDef,
|
|
@@ -332,6 +334,11 @@ export default {
|
|
|
332
334
|
from: PANEL_ACTION_SINK,
|
|
333
335
|
default: null,
|
|
334
336
|
},
|
|
337
|
+
|
|
338
|
+
/** The app's look, provided by CnAppRoot or CnPageRenderer (`nextcloud` or `board`). */
|
|
339
|
+
cnLook: { default: 'nextcloud' },
|
|
340
|
+
/** The host's translate (CnAppRoot provides `cnTranslate`). */
|
|
341
|
+
cnTranslate: { default: () => (key) => key },
|
|
335
342
|
},
|
|
336
343
|
|
|
337
344
|
/**
|
|
@@ -780,7 +787,9 @@ export default {
|
|
|
780
787
|
integrationProps() {
|
|
781
788
|
return {
|
|
782
789
|
surface: this.surface,
|
|
783
|
-
|
|
790
|
+
// Through the host's translate: an integration card (CnDetailCard)
|
|
791
|
+
// prints its title as given.
|
|
792
|
+
title: this.translateLabel(this.widget?.title || ''),
|
|
784
793
|
// A provider opting into `bareWidget` is being rendered where the
|
|
785
794
|
// tab strip already supplies the card and the title, so tell it to
|
|
786
795
|
// drop its own. Without this the panel gets a titled card inside
|
|
@@ -836,9 +845,46 @@ export default {
|
|
|
836
845
|
surface: this.surface,
|
|
837
846
|
})
|
|
838
847
|
}
|
|
848
|
+
// The board look: a self-titled card (an audit trail, a calendar)
|
|
849
|
+
// takes the manifest title, so the side column's History card says
|
|
850
|
+
// what the board says instead of the widget's own default.
|
|
851
|
+
const title = this.cardTitle
|
|
852
|
+
if (title) {
|
|
853
|
+
base.title = title
|
|
854
|
+
}
|
|
839
855
|
return { ...base, ...this.content }
|
|
840
856
|
},
|
|
841
857
|
|
|
858
|
+
/**
|
|
859
|
+
* The title a self-titled renderer gets under the board look: the
|
|
860
|
+
* manifest title through the host's translate, else History for the
|
|
861
|
+
* activity widget. Empty outside the board look, in a tab panel, and
|
|
862
|
+
* when `content.title` already names the card.
|
|
863
|
+
*
|
|
864
|
+
* @return {string} The title, or ''.
|
|
865
|
+
* @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-a-side-card-takes-its-manifest-title
|
|
866
|
+
*/
|
|
867
|
+
cardTitle() {
|
|
868
|
+
if (normalizeLook(this.cnLook) !== 'board' || this.isBare || !this.widget || this.isContentOnly || this.isCard) {
|
|
869
|
+
return ''
|
|
870
|
+
}
|
|
871
|
+
// Only a renderer that takes a `title` prop: on any other the value
|
|
872
|
+
// would land on its root element as a tooltip.
|
|
873
|
+
const declared = this.renderer && this.renderer.props
|
|
874
|
+
const takesTitle = Array.isArray(declared) ? declared.includes('title') : Boolean(declared && Object.hasOwn(declared, 'title'))
|
|
875
|
+
if (!takesTitle) {
|
|
876
|
+
return ''
|
|
877
|
+
}
|
|
878
|
+
if (typeof this.content.title === 'string' && this.content.title !== '') {
|
|
879
|
+
return ''
|
|
880
|
+
}
|
|
881
|
+
const title = widgetTitleOf(this.widget)
|
|
882
|
+
if (title) {
|
|
883
|
+
return this.translateLabel(title)
|
|
884
|
+
}
|
|
885
|
+
return isActivityWidget(this.widget) ? t('nextcloud-vue', 'History') : ''
|
|
886
|
+
},
|
|
887
|
+
|
|
842
888
|
/**
|
|
843
889
|
* Whether the type declares itself a container (registry `container`).
|
|
844
890
|
*
|
|
@@ -946,6 +992,20 @@ export default {
|
|
|
946
992
|
},
|
|
947
993
|
|
|
948
994
|
methods: {
|
|
995
|
+
/**
|
|
996
|
+
* A manifest label through the host's translate; '' stays ''.
|
|
997
|
+
*
|
|
998
|
+
* @param {string} text The label as written in the manifest.
|
|
999
|
+
* @return {string} The label in the user's language.
|
|
1000
|
+
* @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-detail-labels-read-in-the-users-language
|
|
1001
|
+
*/
|
|
1002
|
+
translateLabel(text) {
|
|
1003
|
+
if (typeof text !== 'string' || text === '') {
|
|
1004
|
+
return ''
|
|
1005
|
+
}
|
|
1006
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
1007
|
+
},
|
|
1008
|
+
|
|
949
1009
|
/**
|
|
950
1010
|
* Re-key a call on the channel this host provides onto the one it holds.
|
|
951
1011
|
* A widget publishes `set(items)`; a host nested in this one speaks the
|