@conduction/nextcloud-vue 2.69.0 → 2.71.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/CnActionsBar/CnActionsBar.vue2.js +25 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +40 -43
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +54 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +165 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +69 -26
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +27 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +11 -0
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +4 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useWalkthrough.js +11 -3
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -0
- package/dist/esm/utils/capabilityComparison.js +29 -1
- package/dist/esm/utils/capabilityComparison.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +54 -4
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/userAutocomplete.js +69 -3
- package/dist/esm/utils/userAutocomplete.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2778 -1630
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
- package/src/components/CnAppNav/CnAppNav.vue +85 -8
- package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
- package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +46 -19
- package/src/components/CnDataTable/CnDataTable.vue +35 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +57 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
- package/src/components/CnIndexPage/CnIndexPage.vue +194 -7
- package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
- package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +36 -0
- package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
- package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +13 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
- package/src/composables/useWalkthrough.js +11 -3
- package/src/css/dashboard.css +44 -0
- package/src/css/detail-page.css +10 -0
- package/src/css/index-page.css +12 -0
- package/src/css/kpi-card.css +15 -0
- package/src/schemas/app-manifest-v2.schema.json +47 -5
- package/src/utils/capabilityComparison.js +28 -0
- package/src/utils/columnFilters.js +54 -4
- package/src/utils/userAutocomplete.js +68 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<div class="cn-week-strip" data-testid="cn-week-strip">
|
|
6
|
+
<div class="cn-week-strip" :class="{ 'cn-week-strip--inset': inset }" data-testid="cn-week-strip">
|
|
7
7
|
<div v-if="loading" class="cn-week-strip__state" role="status">
|
|
8
8
|
<NcLoadingIcon :size="20" />
|
|
9
9
|
<span class="cn-week-strip__state-text">{{ loadingLabel }}</span>
|
|
@@ -118,9 +118,11 @@ export default {
|
|
|
118
118
|
* today), `lateField` a boolean field that marks a record late.
|
|
119
119
|
* `items` are static entries `{ title, meta?, date, route?, href?, late? }`.
|
|
120
120
|
* `emptyText` is the text of a day without items. `weekOffset` moves
|
|
121
|
-
* the strip whole weeks from the current one.
|
|
121
|
+
* the strip whole weeks from the current one. `inset: true` draws the
|
|
122
|
+
* strip inside the board's inset (16px above, 24px at the sides, 22px
|
|
123
|
+
* below) instead of from card edge to card edge.
|
|
122
124
|
*
|
|
123
|
-
* @type {{days?: (5|7), source?: {register?: string, schema?: string, filter?: object, limit?: number}, dateField?: string, titleField?: string, metaFields?: Array<string>, itemRoute?: string, lateWhen?: {op?: string, value?: number}, lateField?: string, items?: Array<{title: string, meta?: string, date: string, route?: (string|object), href?: string, late?: boolean}>, emptyText?: string, weekOffset?: number}}
|
|
125
|
+
* @type {{days?: (5|7), inset?: boolean, source?: {register?: string, schema?: string, filter?: object, limit?: number}, dateField?: string, titleField?: string, metaFields?: Array<string>, itemRoute?: string, lateWhen?: {op?: string, value?: number}, lateField?: string, items?: Array<{title: string, meta?: string, date: string, route?: (string|object), href?: string, late?: boolean}>, emptyText?: string, weekOffset?: number}}
|
|
124
126
|
*/
|
|
125
127
|
content: {
|
|
126
128
|
type: Object,
|
|
@@ -159,6 +161,17 @@ export default {
|
|
|
159
161
|
},
|
|
160
162
|
|
|
161
163
|
computed: {
|
|
164
|
+
/**
|
|
165
|
+
* Whether the widget draws the board's inset (`content.inset`). Off
|
|
166
|
+
* by default, which keeps it edge to edge as before.
|
|
167
|
+
*
|
|
168
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-strip-and-a-stacked-bar-can-take-the-board-inset
|
|
169
|
+
* @return {boolean}
|
|
170
|
+
*/
|
|
171
|
+
inset() {
|
|
172
|
+
return Boolean(this.content && this.content.inset === true)
|
|
173
|
+
},
|
|
174
|
+
|
|
162
175
|
/**
|
|
163
176
|
* The translate function in use.
|
|
164
177
|
*
|
|
@@ -542,6 +555,14 @@ export default {
|
|
|
542
555
|
width: 100%;
|
|
543
556
|
}
|
|
544
557
|
|
|
558
|
+
/* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-
|
|
559
|
+
gaps-3). A dashboard renders its widgets flush, so without it the strip ran
|
|
560
|
+
from card edge to card edge. Theme hook: --cn-widget-board-inset. */
|
|
561
|
+
.cn-week-strip--inset {
|
|
562
|
+
box-sizing: border-box;
|
|
563
|
+
padding: var(--cn-widget-board-inset, 16px 24px 22px);
|
|
564
|
+
}
|
|
565
|
+
|
|
545
566
|
.cn-week-strip__state {
|
|
546
567
|
display: flex;
|
|
547
568
|
align-items: center;
|
|
@@ -216,11 +216,13 @@ export function walkthroughProgressKey(configKey) {
|
|
|
216
216
|
|
|
217
217
|
/**
|
|
218
218
|
* Normalise stored progress (an object, or the JSON string a preference
|
|
219
|
-
* holds) into `{ tourId, stepId, index, version }`,
|
|
219
|
+
* holds) into `{ tourId, stepId, index, version }`, plus `paused: true`
|
|
220
|
+
* when the user paused the tour, or null.
|
|
220
221
|
*
|
|
221
222
|
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
223
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
222
224
|
* @param {unknown} value The stored value.
|
|
223
|
-
* @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.
|
|
225
|
+
* @return {{tourId: string, stepId: string, index: number, version: string, paused: (boolean|undefined)}|null} The progress.
|
|
224
226
|
*/
|
|
225
227
|
export function normaliseWalkthroughProgress(value) {
|
|
226
228
|
let data = value
|
|
@@ -237,12 +239,18 @@ export function normaliseWalkthroughProgress(value) {
|
|
|
237
239
|
if (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {
|
|
238
240
|
return null
|
|
239
241
|
}
|
|
240
|
-
|
|
242
|
+
const progress = {
|
|
241
243
|
tourId: data.tourId,
|
|
242
244
|
stepId: typeof data.stepId === 'string' ? data.stepId : '',
|
|
243
245
|
index: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,
|
|
244
246
|
version: typeof data.version === 'string' ? data.version : '',
|
|
245
247
|
}
|
|
248
|
+
// A paused tour stays hidden on the next visit until the user picks
|
|
249
|
+
// "Continue". Only written when true, so older values read unchanged.
|
|
250
|
+
if (data.paused === true) {
|
|
251
|
+
progress.paused = true
|
|
252
|
+
}
|
|
253
|
+
return progress
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
/**
|
package/src/css/dashboard.css
CHANGED
|
@@ -129,3 +129,47 @@
|
|
|
129
129
|
.cn-requires-app.cn-requires-app .empty-content__action {
|
|
130
130
|
margin-top: var(--default-grid-baseline, 4px);
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/* NcEmptyContent forces its icon's svg to 64px with !important while the
|
|
134
|
+
* rule above lets the icon box shrink, so the svg spilled out of its box and
|
|
135
|
+
* over the name. Hold the svg to the size the caller asked for. */
|
|
136
|
+
.cn-requires-app.cn-requires-app .empty-content__icon svg {
|
|
137
|
+
width: 32px !important;
|
|
138
|
+
height: 32px !important;
|
|
139
|
+
max-width: 32px !important;
|
|
140
|
+
max-height: 32px !important;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* The dashboard's compact "Install <app>" placeholder: two lines of text and
|
|
144
|
+
* the button, nothing else, so it fits a two-row tile even under a title row.
|
|
145
|
+
* `safe center` keeps the top in view when a tile is smaller still, and the
|
|
146
|
+
* body scrolls rather than clipping the button. */
|
|
147
|
+
.cn-requires-app--compact {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: safe center;
|
|
152
|
+
gap: calc(2 * var(--default-grid-baseline, 4px));
|
|
153
|
+
box-sizing: border-box;
|
|
154
|
+
height: 100%;
|
|
155
|
+
min-height: 0;
|
|
156
|
+
overflow: auto;
|
|
157
|
+
padding: calc(2 * var(--default-grid-baseline, 4px));
|
|
158
|
+
text-align: center;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.cn-requires-app--compact .cn-requires-app__description {
|
|
162
|
+
display: -webkit-box;
|
|
163
|
+
margin: 0;
|
|
164
|
+
overflow: hidden;
|
|
165
|
+
color: var(--color-text-maxcontrast);
|
|
166
|
+
font-size: 0.9em;
|
|
167
|
+
line-height: 1.4;
|
|
168
|
+
-webkit-box-orient: vertical;
|
|
169
|
+
-webkit-line-clamp: 2;
|
|
170
|
+
line-clamp: 2;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.cn-requires-app--compact .button-vue {
|
|
174
|
+
flex-shrink: 0;
|
|
175
|
+
}
|
package/src/css/detail-page.css
CHANGED
|
@@ -46,6 +46,16 @@
|
|
|
46
46
|
row-gap: var(--cn-detail-header-row-gap, 22px);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/* That wrap must not take the actions off the title row. With `flex-basis:
|
|
50
|
+
auto` a long title claimed its whole text width, so the actions wrapped
|
|
51
|
+
under it. From a zero basis the title takes what the actions leave and
|
|
52
|
+
wraps (two lines, then an ellipsis) inside it; only on a page too narrow
|
|
53
|
+
for 16rem of title beside the actions do the actions drop a row. */
|
|
54
|
+
.cn-detail-page__header--with-widget > :first-child {
|
|
55
|
+
flex: 1 1 0;
|
|
56
|
+
min-width: min(100%, 16rem);
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
.cn-detail-page__header-widget {
|
|
50
60
|
flex: 1 0 100%;
|
|
51
61
|
min-width: 0;
|
package/src/css/index-page.css
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
padding: calc(5 * var(--default-grid-baseline));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/* The board's header buttons (`headerButtons`) at the end of the title row,
|
|
14
|
+
centred on the title and its count line. They wrap under the title on a
|
|
15
|
+
narrow page. */
|
|
16
|
+
.cn-index-page__header-buttons {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-wrap: wrap;
|
|
19
|
+
align-items: center;
|
|
20
|
+
align-self: center;
|
|
21
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
22
|
+
margin-inline-start: auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
.cn-index-page__below-header {
|
|
14
26
|
margin-bottom: calc(4 * var(--default-grid-baseline));
|
|
15
27
|
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -414,6 +414,21 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
414
414
|
text-decoration: none !important;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
/* A stacked stats block (CnStatsBlock `layout: "stacked"`): its unit
|
|
418
|
+
("decisions") goes on a muted line of its own under the number, where the
|
|
419
|
+
stat tile puts its caption (zuiddrecht-pixel-gaps-3). */
|
|
420
|
+
.cn-kpi-card--stacked .cn-stats-block__count {
|
|
421
|
+
flex-direction: column;
|
|
422
|
+
align-items: flex-start;
|
|
423
|
+
gap: var(--cn-kpi-stacked-gap, 6px);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.cn-kpi-card--stacked .cn-stats-block__count-label {
|
|
427
|
+
font-size: var(--cn-kpi-label-size, 13px);
|
|
428
|
+
font-weight: 500;
|
|
429
|
+
color: var(--color-text-maxcontrast);
|
|
430
|
+
}
|
|
431
|
+
|
|
417
432
|
/* Filled: the card draws its own grey box, for a KPI that is NOT already
|
|
418
433
|
inside a wrapper that draws one. Opt-in via `content.flat: false`. */
|
|
419
434
|
.cn-kpi-card--filled {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.53.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"enabled": { "type": "boolean", "default": true, "description": "Whether the setup wizard is active for this app." },
|
|
65
65
|
"version": { "type": "integer", "default": 1, "description": "Setup contract version; bump to re-trigger setup after a breaking change." },
|
|
66
66
|
"completionConfigKey": { "type": "string", "description": "App-config key storing the completed setup version." },
|
|
67
|
+
"dismissAction": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Setup action that records a closed or finished setup wizard on the server, so it stays closed in every browser. CnAppRoot posts `/apps/{appId}/api/setup/action/{dismissAction}` with `{ finished }` once when the user closes or finishes the wizard. The app either answers the outstanding optional steps or reports `dismissed` (true, or the setup version it was closed at) in `GET /api/setup/status`. Without it, the close is remembered only in the browser (localStorage), as before." },
|
|
67
68
|
"steps": {
|
|
68
69
|
"type": "array",
|
|
69
70
|
"description": "Ordered setup steps rendered by CnSetupWizard.",
|
|
@@ -1569,6 +1570,11 @@
|
|
|
1569
1570
|
"href": { "type": "string" }
|
|
1570
1571
|
}
|
|
1571
1572
|
},
|
|
1573
|
+
"showButtons": {
|
|
1574
|
+
"type": "boolean",
|
|
1575
|
+
"default": true,
|
|
1576
|
+
"description": "`false` drops the widget's footer links (its definition's `buttons`, e.g. a calendar's \"More events\"), for a placement whose card says enough without them (zuiddrecht-pixel-gaps-3)."
|
|
1577
|
+
},
|
|
1572
1578
|
"sizeToContent": {
|
|
1573
1579
|
"type": ["boolean", "integer"],
|
|
1574
1580
|
"minimum": 1,
|
|
@@ -2195,7 +2201,8 @@
|
|
|
2195
2201
|
}
|
|
2196
2202
|
},
|
|
2197
2203
|
"emptyText": { "type": "string", "description": "Text of a day without items." },
|
|
2198
|
-
"weekOffset": { "type": "integer", "description": "Whole weeks from the current one (1 = next week)." }
|
|
2204
|
+
"weekOffset": { "type": "integer", "description": "Whole weeks from the current one (1 = next week)." },
|
|
2205
|
+
"inset": { "type": "boolean", "default": false, "description": "Draw the strip inside the board's inset (16px above, 24px at the sides, 22px below) instead of from card edge to card edge (zuiddrecht-pixel-gaps-3). Theme hook --cn-widget-board-inset." }
|
|
2199
2206
|
}
|
|
2200
2207
|
}
|
|
2201
2208
|
}
|
|
@@ -2246,7 +2253,8 @@
|
|
|
2246
2253
|
},
|
|
2247
2254
|
"order": { "type": "array", "items": { "type": "string" }, "description": "Group keys in the order they render. Unlisted groups follow; a listed key without records shows 0." },
|
|
2248
2255
|
"labels": { "type": "object", "additionalProperties": { "type": "string" }, "description": "The label of a group key." },
|
|
2249
|
-
"emptyText": { "type": "string", "description": "Shown when the total is 0." }
|
|
2256
|
+
"emptyText": { "type": "string", "description": "Shown when the total is 0." },
|
|
2257
|
+
"inset": { "type": "boolean", "default": false, "description": "Draw the bar and legend inside the board's inset (16px above, 24px at the sides, 22px below) instead of from card edge to card edge (zuiddrecht-pixel-gaps-3). Theme hook --cn-widget-board-inset." }
|
|
2250
2258
|
}
|
|
2251
2259
|
}
|
|
2252
2260
|
}
|
|
@@ -3681,7 +3689,12 @@
|
|
|
3681
3689
|
"showWidgetActions": {
|
|
3682
3690
|
"type": "boolean",
|
|
3683
3691
|
"default": true,
|
|
3684
|
-
"description": "type='dashboard': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2). Maps to CnDashboardPage `showWidgetActions`."
|
|
3692
|
+
"description": "type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."
|
|
3693
|
+
},
|
|
3694
|
+
"gridFloat": {
|
|
3695
|
+
"type": "boolean",
|
|
3696
|
+
"default": true,
|
|
3697
|
+
"description": "type='dashboard': `false` packs widgets upward (GridStack `float: false`), so a size-to-content widget that shrinks closes the gap under it instead of leaving an empty band (zuiddrecht-pixel-gaps-3). Maps to CnDashboardPage `gridFloat`."
|
|
3685
3698
|
},
|
|
3686
3699
|
"showTitle": {
|
|
3687
3700
|
"type": "boolean",
|
|
@@ -3692,6 +3705,33 @@
|
|
|
3692
3705
|
"type": "string",
|
|
3693
3706
|
"description": "type='index': an i18n key for the description line with `{total}` replaced by the collection's total, e.g. \"{total} open cases\" (zuiddrecht-pixel-gaps). Shown in place of `description` once a total is known. Maps to CnIndexPage `countSubtitle`."
|
|
3694
3707
|
},
|
|
3708
|
+
"showTitleIcon": {
|
|
3709
|
+
"type": "boolean",
|
|
3710
|
+
"default": true,
|
|
3711
|
+
"description": "type='index': `false` drops the icon before the page title, for a title that stands alone as the board draws it (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showTitleIcon`."
|
|
3712
|
+
},
|
|
3713
|
+
"showCount": {
|
|
3714
|
+
"type": "boolean",
|
|
3715
|
+
"default": true,
|
|
3716
|
+
"description": "type='index': `false` drops the actions bar's \"Showing 20 of 258\" line, for a page whose title line already gives the total through `countSubtitle` (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showCount`."
|
|
3717
|
+
},
|
|
3718
|
+
"headerButtons": {
|
|
3719
|
+
"type": "array",
|
|
3720
|
+
"description": "type='index': buttons beside the page title, as the board's \"Export\" and \"New case\" (zuiddrecht-pixel-gaps-3). `action` is `add` (the Add flow; the label defaults to the Add label), `export` (the export leaf in `format`, else the export dialog), `import`, `refresh`, or the id of a `headerActions` entry. Shown only with `showTitle`; when they show, the actions bar drops its Views and Actions menus, and the Add button and Export menu when a button takes their action. Maps to CnIndexPage `headerButtons`.",
|
|
3721
|
+
"items": {
|
|
3722
|
+
"type": "object",
|
|
3723
|
+
"additionalProperties": false,
|
|
3724
|
+
"required": ["action"],
|
|
3725
|
+
"properties": {
|
|
3726
|
+
"id": { "type": "string", "minLength": 1 },
|
|
3727
|
+
"label": { "type": "string", "minLength": 1, "description": "i18n key; optional for `add`." },
|
|
3728
|
+
"action": { "type": "string", "minLength": 1 },
|
|
3729
|
+
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "secondary" },
|
|
3730
|
+
"icon": { "type": "string", "minLength": 1, "description": "MDI icon name." },
|
|
3731
|
+
"format": { "type": "string", "enum": ["csv", "excel"], "default": "csv", "description": "For `export`." }
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
},
|
|
3695
3735
|
"showTypeEyebrow": {
|
|
3696
3736
|
"type": "boolean",
|
|
3697
3737
|
"default": true,
|
|
@@ -3707,7 +3747,9 @@
|
|
|
3707
3747
|
"route": { "type": "string" },
|
|
3708
3748
|
"params": { "type": "object", "additionalProperties": true },
|
|
3709
3749
|
"href": { "type": "string" },
|
|
3710
|
-
"icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name (e.g. `Home`) drawn as the first crumb instead of the label, which then becomes its accessible name (zuiddrecht-pixel-gaps-2). Without it the label shows as text." }
|
|
3750
|
+
"icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name (e.g. `Home`) drawn as the first crumb instead of the label, which then becomes its accessible name (zuiddrecht-pixel-gaps-2). Without it the label shows as text." },
|
|
3751
|
+
"currentField": { "type": "string", "minLength": 1, "description": "A field of the record (dotted path, e.g. `identifier`) whose value is the current crumb instead of the display name; an empty value falls back to the display name (zuiddrecht-pixel-gaps-3)." },
|
|
3752
|
+
"separator": { "type": "string", "minLength": 1, "maxLength": 3, "description": "Text drawn between the crumbs (e.g. `/`) instead of the chevron (zuiddrecht-pixel-gaps-3)." }
|
|
3711
3753
|
}
|
|
3712
3754
|
},
|
|
3713
3755
|
"headerCard": {
|
|
@@ -22,6 +22,34 @@
|
|
|
22
22
|
* @module utils/capabilityComparison
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
/*
|
|
26
|
+
* `process.env.NODE_ENV` is substituted by every consuming app's bundler at
|
|
27
|
+
* build time (webpack's DefinePlugin, rollup's replace); see NcSelectTags.js.
|
|
28
|
+
*/
|
|
29
|
+
/* global process */
|
|
30
|
+
|
|
31
|
+
let deprecationWarned = false
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Warn once per page load, in a development build only, that the in-app
|
|
35
|
+
* capability comparison is deprecated. Called by CnFeaturesAndRoadmapPage,
|
|
36
|
+
* CnFeaturesAndRoadmapView and CnCapabilityTable when a comparison is
|
|
37
|
+
* actually given, so an app that passes none hears nothing.
|
|
38
|
+
*
|
|
39
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
|
|
40
|
+
* @param {string} source What carried the comparison, for the message.
|
|
41
|
+
* @return {boolean} True when this call printed the warning.
|
|
42
|
+
*/
|
|
43
|
+
export function warnCapabilityComparisonDeprecated(source) {
|
|
44
|
+
if (deprecationWarned || process.env.NODE_ENV === 'production') {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
deprecationWarned = true
|
|
48
|
+
// eslint-disable-next-line no-console -- a deliberate deprecation notice to the developer integrating the component
|
|
49
|
+
console.warn(`[nextcloud-vue] ${source} is deprecated: the in-app capability comparison (capabilityComparison, the features_roadmap_capabilities initial state, CnCapabilityTable) will be removed in a future major version. Publish the comparison on the app's docs site and link to it instead.`)
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
/**
|
|
26
54
|
* Ratings in the order they are counted and rendered.
|
|
27
55
|
*
|
|
@@ -161,15 +161,65 @@ function referenceTarget(column, prop) {
|
|
|
161
161
|
return { schema, register, labelField }
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Whether a schema lists its properties. Without that the table knows its
|
|
166
|
+
* columns but not what backs them.
|
|
167
|
+
*
|
|
168
|
+
* @param {object|null} schema The table's schema.
|
|
169
|
+
* @return {boolean} True when the schema has a properties map.
|
|
170
|
+
*/
|
|
171
|
+
function hasProperties(schema) {
|
|
172
|
+
return !!(schema && typeof schema === 'object' && schema.properties && typeof schema.properties === 'object')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* A filter for a column when the table has no schema to read (a manifest
|
|
177
|
+
* page whose schema did not resolve, a host-fed table). The column's own
|
|
178
|
+
* hints decide the kind: an `fkResolve` widget gives a reference filter, a
|
|
179
|
+
* `type` or `format` on the column gives number, date or yes/no, and every
|
|
180
|
+
* other plain stored key gets a text filter on contains (`[like]`). A key
|
|
181
|
+
* that is a path (`a.b`) or a metadata field (`@self`, `_id`) gets none,
|
|
182
|
+
* because OpenRegister does not filter those by that name.
|
|
183
|
+
*
|
|
184
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-data-table/spec.md
|
|
185
|
+
* @param {object} column The column definition.
|
|
186
|
+
* @param {object} base The shared part of the definition.
|
|
187
|
+
* @return {object|null} The filter definition.
|
|
188
|
+
*/
|
|
189
|
+
function columnHintFilterDef(column, base) {
|
|
190
|
+
const key = column.key
|
|
191
|
+
if (key.includes('.') || key.charAt(0) === '@' || key.charAt(0) === '_') {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
const reference = referenceTarget(column, null)
|
|
195
|
+
if (reference) {
|
|
196
|
+
return { ...base, kind: 'reference', reference }
|
|
197
|
+
}
|
|
198
|
+
const type = String(column.type || '').toLowerCase()
|
|
199
|
+
const format = String(column.format || '').toLowerCase()
|
|
200
|
+
if (type === 'boolean') {
|
|
201
|
+
return { ...base, kind: 'boolean' }
|
|
202
|
+
}
|
|
203
|
+
if (type === 'number' || type === 'integer') {
|
|
204
|
+
return { ...base, kind: 'number' }
|
|
205
|
+
}
|
|
206
|
+
if (type === 'date' || type === 'date-time' || format === 'date' || format === 'date-time') {
|
|
207
|
+
return { ...base, kind: 'date', dateTime: type === 'date-time' || format === 'date-time' }
|
|
208
|
+
}
|
|
209
|
+
return { ...base, kind: 'string' }
|
|
210
|
+
}
|
|
211
|
+
|
|
164
212
|
/**
|
|
165
213
|
* How a column filters, or null when it does not.
|
|
166
214
|
*
|
|
167
215
|
* Kinds: `enum` (checkbox list), `boolean` (yes, no, any), `reference`
|
|
168
216
|
* (searchable list of the referenced objects), `number` and `date` (from and
|
|
169
217
|
* to), and `string` (contains, through OpenRegister's `[like]`). `filterable: false` on the column turns it
|
|
170
|
-
* off.
|
|
171
|
-
* carries an enum hint (`enum`, or a badge
|
|
172
|
-
* server cannot filter on a value it does
|
|
218
|
+
* off. When the schema lists its properties, a column with no property
|
|
219
|
+
* behind it only filters when it carries an enum hint (`enum`, or a badge
|
|
220
|
+
* widget's colour map), because the server cannot filter on a value it does
|
|
221
|
+
* not store. When there is no schema to read, the column's own hints decide
|
|
222
|
+
* (see `columnHintFilterDef`), falling back to a text filter on contains.
|
|
173
223
|
*
|
|
174
224
|
* @param {object} column The column definition.
|
|
175
225
|
* @param {object|null} schema The table's schema.
|
|
@@ -191,7 +241,7 @@ export function columnFilterDef(column, schema) {
|
|
|
191
241
|
return { ...base, kind: 'enum', options }
|
|
192
242
|
}
|
|
193
243
|
if (!prop) {
|
|
194
|
-
return null
|
|
244
|
+
return hasProperties(schema) ? null : columnHintFilterDef(column, base)
|
|
195
245
|
}
|
|
196
246
|
const reference = referenceTarget(column, prop)
|
|
197
247
|
if (reference) {
|
|
@@ -12,9 +12,50 @@
|
|
|
12
12
|
* @module utils/userAutocomplete
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
15
16
|
import axios from '@nextcloud/axios'
|
|
16
17
|
import { generateOcsUrl } from '@nextcloud/router'
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The signed-in user as a picker option, or null when nobody is signed in.
|
|
21
|
+
*
|
|
22
|
+
* Core autocomplete never returns the person who searches (it is built for
|
|
23
|
+
* sharing, and you do not share with yourself), so a picker backed by it
|
|
24
|
+
* alone cannot assign a task to yourself.
|
|
25
|
+
*
|
|
26
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
|
|
27
|
+
* @return {{id: string, label: string, displayName: string, subline: string}|null} The option.
|
|
28
|
+
*/
|
|
29
|
+
function currentUserOption() {
|
|
30
|
+
let user
|
|
31
|
+
try {
|
|
32
|
+
user = getCurrentUser()
|
|
33
|
+
} catch {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
if (!user || !user.uid) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
const name = user.displayName || String(user.uid)
|
|
40
|
+
return { id: String(user.uid), label: name, displayName: name, subline: '' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether the current-user option matches a search term (uid or display
|
|
45
|
+
* name contains the term, ignoring case). An empty term matches.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} option The current-user option.
|
|
48
|
+
* @param {string} query The search term.
|
|
49
|
+
* @return {boolean} True when it matches.
|
|
50
|
+
*/
|
|
51
|
+
function matchesQuery(option, query) {
|
|
52
|
+
const q = String(query || '').trim().toLowerCase()
|
|
53
|
+
if (q === '') {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
return option.id.toLowerCase().includes(q) || option.label.toLowerCase().includes(q)
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
/**
|
|
19
60
|
* Map one autocomplete suggestion to a picker option, keeping only user
|
|
20
61
|
* results. The OCS payload uses `id` for the UID, `label` for the display
|
|
@@ -59,10 +100,11 @@ function toUserOption(suggestion) {
|
|
|
59
100
|
* @param {string} [query] The search term (empty loads an initial page).
|
|
60
101
|
* @param {object} [options] Tuning options.
|
|
61
102
|
* @param {number} [options.limit] Max results (default 25).
|
|
103
|
+
* @param {boolean} [options.includeCurrentUser] Add the signed-in user when they match (default true; a mention list passes false).
|
|
62
104
|
* @return {Promise<Array<{id: string, label: string, subline: string}>>} Picker options.
|
|
63
105
|
*/
|
|
64
106
|
export async function searchNextcloudUsers(query = '', options = {}) {
|
|
65
|
-
const { limit = 25 } = options
|
|
107
|
+
const { limit = 25, includeCurrentUser = true } = options
|
|
66
108
|
try {
|
|
67
109
|
const url = generateOcsUrl('core/autocomplete/get')
|
|
68
110
|
const response = await axios.get(url, {
|
|
@@ -86,14 +128,32 @@ export async function searchNextcloudUsers(query = '', options = {}) {
|
|
|
86
128
|
const list = Array.isArray(data)
|
|
87
129
|
? data
|
|
88
130
|
: (Array.isArray(response && response.data) ? response.data : [])
|
|
89
|
-
|
|
131
|
+
const found = list
|
|
90
132
|
.map(toUserOption)
|
|
91
133
|
.filter((opt) => opt !== null)
|
|
134
|
+
return includeCurrentUser ? withCurrentUser(found, query) : found
|
|
92
135
|
} catch {
|
|
93
136
|
return []
|
|
94
137
|
}
|
|
95
138
|
}
|
|
96
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Put the signed-in user first in a result list when they match the search
|
|
142
|
+
* and the server left them out.
|
|
143
|
+
*
|
|
144
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
|
|
145
|
+
* @param {Array<object>} options The server's options.
|
|
146
|
+
* @param {string} query The search term.
|
|
147
|
+
* @return {Array<object>} The options, the current user included when they match.
|
|
148
|
+
*/
|
|
149
|
+
function withCurrentUser(options, query) {
|
|
150
|
+
const me = currentUserOption()
|
|
151
|
+
if (!me || !matchesQuery(me, query) || options.some((opt) => opt.id === me.id)) {
|
|
152
|
+
return options
|
|
153
|
+
}
|
|
154
|
+
return [me, ...options]
|
|
155
|
+
}
|
|
156
|
+
|
|
97
157
|
/**
|
|
98
158
|
* Resolve a single user UID to a `{ id, label }` option for edit-mode display.
|
|
99
159
|
* Tries the autocomplete endpoint with the UID as the query and matches the
|
|
@@ -108,6 +168,12 @@ export async function resolveNextcloudUser(uid) {
|
|
|
108
168
|
if (uid === undefined || uid === null || uid === '') {
|
|
109
169
|
return fallback
|
|
110
170
|
}
|
|
171
|
+
// The server never returns the signed-in user, so their own uid would
|
|
172
|
+
// otherwise fall back to the bare uid.
|
|
173
|
+
const me = currentUserOption()
|
|
174
|
+
if (me && me.id === String(uid)) {
|
|
175
|
+
return me
|
|
176
|
+
}
|
|
111
177
|
const results = await searchNextcloudUsers(String(uid))
|
|
112
178
|
const match = results.find((opt) => opt.id === String(uid))
|
|
113
179
|
return match || fallback
|