@conduction/nextcloud-vue 2.97.0 → 2.99.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 +12 -0
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +11 -2
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +17 -0
- 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/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +106 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -8
- 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 +188 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +3 -3
- 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/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +27 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +43 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +239 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +182 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +9 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +265 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +191 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +9 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +508 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js +99 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +18 -70
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +6 -44
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +2 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +9 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js +472 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js +231 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/index.js +27 -0
- package/dist/esm/components/CnWorkingCalendarWidget/index.js.map +1 -0
- package/dist/esm/composables/indexSources.js +238 -2
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useFlowStore.js +2 -1
- package/dist/esm/composables/useFlowStore.js.map +1 -1
- package/dist/esm/composables/useOrganisationStore.js +115 -0
- package/dist/esm/composables/useOrganisationStore.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +109 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +138 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1071 -274
- package/dist/esm/utils/builtInFormatters.js +34 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/matrixCells.js +158 -0
- package/dist/esm/utils/matrixCells.js.map +1 -0
- package/dist/esm/utils/nextcloudDirectory.js +121 -0
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/workingCalendarMonth.js +85 -0
- package/dist/esm/utils/workingCalendarMonth.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +14888 -10014
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1071 -274
- package/l10n/en.json +109 -1
- package/l10n/nl.json +138 -1
- package/package.json +2 -2
- package/scripts/merge-l10n.js +196 -0
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAppRoot/CnAppRoot.md +1 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +1 -0
- package/src/components/CnChartWidget/CnChartWidget.md +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
- package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
- package/src/components/CnChecklistRunWidget/index.js +27 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +26 -8
- package/src/components/CnDetailPage/CnDetailPage.md +48 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +189 -5
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
- package/src/components/CnIndexPage/CnIndexPage.md +28 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +43 -3
- package/src/components/CnOrderedListField/CnOrderedListField.vue +458 -0
- package/src/components/CnOrderedListField/index.js +7 -0
- package/src/components/CnSettingsField/CnSettingsField.vue +449 -0
- package/src/components/CnSettingsField/index.js +4 -0
- package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +613 -0
- package/src/components/CnSettingsFieldsSection/index.js +4 -0
- package/src/components/CnSettingsPage/CnSettingsPage.vue +6 -86
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +2 -0
- package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +725 -0
- package/src/components/CnWorkingCalendarWidget/index.js +26 -0
- package/src/components/index.js +5 -0
- package/src/composables/indexSources.js +237 -1
- package/src/composables/useFlowStore.js +2 -1
- package/src/composables/useOrganisationStore.js +111 -0
- package/src/css/badge.css +32 -16
- package/src/css/look-board-detail.css +80 -8
- package/src/css/look-board-index.css +52 -8
- package/src/css/look-board.css +219 -1
- package/src/index.js +5 -0
- package/src/schemas/app-manifest-v2.schema.json +33 -3
- package/src/utils/builtInFormatters.js +33 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/matrixCells.js +154 -0
- package/src/utils/nextcloudDirectory.js +117 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/workingCalendarMonth.js +82 -0
|
@@ -417,6 +417,38 @@ export function formatConnectionSettingsLabel(value) {
|
|
|
417
417
|
: ''
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Show a value with all but its last characters replaced by `*`, as the facts
|
|
422
|
+
* widget's `masked` format does: an IBAN or a BSN reads `*****4567`.
|
|
423
|
+
*
|
|
424
|
+
* DISPLAY ONLY, NOT ACCESS CONTROL. The full value is still in the row the
|
|
425
|
+
* page received; a reader with the browser's developer tools can read it.
|
|
426
|
+
* A value the reader must not have belongs hidden in OpenRegister (property
|
|
427
|
+
* authorization), not masked here.
|
|
428
|
+
*
|
|
429
|
+
* The column's `formatterOptions.visible` sets how many characters stay
|
|
430
|
+
* (default 4); at most five `*` are drawn, so the length does not leak.
|
|
431
|
+
* Null-safe: `''` for null or empty.
|
|
432
|
+
*
|
|
433
|
+
* @param {unknown} value The value.
|
|
434
|
+
* @param {object} [_row] The full row (unused).
|
|
435
|
+
* @param {object} [_property] The schema property (unused).
|
|
436
|
+
* @param {{visible?: number}} [options] The column's `formatterOptions`.
|
|
437
|
+
* @return {string} The masked value.
|
|
438
|
+
* @spec openspec/changes/masked-formatter-max-aggregate/specs/index-page/spec.md#requirement-a-column-can-mask-its-value-for-display
|
|
439
|
+
*/
|
|
440
|
+
export function formatMasked(value, _row, _property, options) {
|
|
441
|
+
if (value === null || value === undefined || value === '') {
|
|
442
|
+
return ''
|
|
443
|
+
}
|
|
444
|
+
const text = String(value)
|
|
445
|
+
const visible = Number.isInteger(options && options.visible) && options.visible >= 0 ? options.visible : 4
|
|
446
|
+
if (text.length <= visible) {
|
|
447
|
+
return text
|
|
448
|
+
}
|
|
449
|
+
return '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')
|
|
450
|
+
}
|
|
451
|
+
|
|
420
452
|
/**
|
|
421
453
|
* Built-in formatter registry merged under any consumer-registered
|
|
422
454
|
* `formatters` in `CnAppRoot`'s `cnFormatters` provide.
|
|
@@ -433,4 +465,5 @@ export const BUILT_IN_FORMATTERS = {
|
|
|
433
465
|
count: formatCount,
|
|
434
466
|
connectionStatus: formatConnectionStatus,
|
|
435
467
|
connectionSettingsLabel: formatConnectionSettingsLabel,
|
|
468
|
+
masked: formatMasked,
|
|
436
469
|
}
|
|
@@ -93,6 +93,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
93
93
|
'banner',
|
|
94
94
|
'calendar',
|
|
95
95
|
'chart',
|
|
96
|
+
'checklist-run',
|
|
96
97
|
'chips-field',
|
|
97
98
|
'choice-picker',
|
|
98
99
|
'container',
|
|
@@ -151,6 +152,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
151
152
|
'toggle-table',
|
|
152
153
|
'video',
|
|
153
154
|
'week-strip',
|
|
155
|
+
'working-calendar',
|
|
154
156
|
'workspace-filter',
|
|
155
157
|
])
|
|
156
158
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* matrixCells: lay out records that each hold one matrix cell as the rows and
|
|
6
|
+
* columns a `matrix` chart draws.
|
|
7
|
+
*
|
|
8
|
+
* A matrix kept as data is often stored one record per cell: the row it sits
|
|
9
|
+
* in, the column, and the value. This turns such a list into one series per
|
|
10
|
+
* row with a value per column, in a stable order, so the chart reads the same
|
|
11
|
+
* matrix the app computes with instead of a copy written into a manifest.
|
|
12
|
+
*
|
|
13
|
+
* @module utils/matrixCells
|
|
14
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-records
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readPath } from './readPath.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A stored key as text, or null when there is none.
|
|
21
|
+
*
|
|
22
|
+
* @param {unknown} value The stored value.
|
|
23
|
+
* @return {string|null} The key.
|
|
24
|
+
*/
|
|
25
|
+
function keyOf(value) {
|
|
26
|
+
if (value === null || value === undefined || value === '' || typeof value === 'object') {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
return String(value)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The keys in the order they are drawn: the listed order first, then keys
|
|
34
|
+
* with an order value (lowest first), then the rest in natural order.
|
|
35
|
+
*
|
|
36
|
+
* @param {Map<string, {order: number}>} found The keys seen, with their lowest order value.
|
|
37
|
+
* @param {Array<string|number>|undefined} listed An explicit order.
|
|
38
|
+
* @return {string[]} The ordered keys.
|
|
39
|
+
*/
|
|
40
|
+
function orderKeys(found, listed) {
|
|
41
|
+
const explicit = Array.isArray(listed) ? listed.map((k) => String(k)).filter((k) => found.has(k)) : []
|
|
42
|
+
const rest = [...found.keys()].filter((k) => !explicit.includes(k))
|
|
43
|
+
rest.sort((a, b) => {
|
|
44
|
+
const oa = found.get(a).order
|
|
45
|
+
const ob = found.get(b).order
|
|
46
|
+
if (oa !== ob) {
|
|
47
|
+
return oa - ob
|
|
48
|
+
}
|
|
49
|
+
return a.localeCompare(b, undefined, { numeric: true })
|
|
50
|
+
})
|
|
51
|
+
return [...explicit, ...rest]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Lay out cell records as matrix rows and columns.
|
|
56
|
+
*
|
|
57
|
+
* Config: `rowField`, `columnField`, `valueField` (paths on a record);
|
|
58
|
+
* optional `rowLabelField` / `columnLabelField` (a path holding the label),
|
|
59
|
+
* `rowLabels` / `columnLabels` (key to label maps, these win), `rowOrder` /
|
|
60
|
+
* `columnOrder` (keys in order) and `rowOrderField` / `columnOrderField` (a
|
|
61
|
+
* numeric path to sort by). A record without a row or column is skipped; when
|
|
62
|
+
* two records name the same cell, the first wins.
|
|
63
|
+
*
|
|
64
|
+
* @param {Array<object>} records The cell records.
|
|
65
|
+
* @param {object} config The `cellSource` config.
|
|
66
|
+
* @return {{series: Array<{key: string, name: string, data: Array<unknown>}>, categories: string[], columnKeys: string[]}} The matrix.
|
|
67
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-records
|
|
68
|
+
*/
|
|
69
|
+
export function pivotCells(records, config) {
|
|
70
|
+
const cfg = config && typeof config === 'object' ? config : {}
|
|
71
|
+
const rows = new Map()
|
|
72
|
+
const columns = new Map()
|
|
73
|
+
const cells = new Map()
|
|
74
|
+
const note = (map, key, label, order) => {
|
|
75
|
+
const entry = map.get(key)
|
|
76
|
+
const value = Number(order)
|
|
77
|
+
const rank = Number.isFinite(value) ? value : Infinity
|
|
78
|
+
if (!entry) {
|
|
79
|
+
map.set(key, { label, order: rank })
|
|
80
|
+
} else {
|
|
81
|
+
entry.order = Math.min(entry.order, rank)
|
|
82
|
+
entry.label = entry.label || label
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const record of Array.isArray(records) ? records : []) {
|
|
86
|
+
if (!record || typeof record !== 'object') {
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
const row = keyOf(readPath(record, cfg.rowField))
|
|
90
|
+
const column = keyOf(readPath(record, cfg.columnField))
|
|
91
|
+
if (row === null || column === null) {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
note(rows, row, cfg.rowLabelField ? keyOf(readPath(record, cfg.rowLabelField)) : null, cfg.rowOrderField ? readPath(record, cfg.rowOrderField) : undefined)
|
|
95
|
+
note(columns, column, cfg.columnLabelField ? keyOf(readPath(record, cfg.columnLabelField)) : null, cfg.columnOrderField ? readPath(record, cfg.columnOrderField) : undefined)
|
|
96
|
+
const cell = `${row}\u0000${column}`
|
|
97
|
+
if (!cells.has(cell)) {
|
|
98
|
+
const value = readPath(record, cfg.valueField)
|
|
99
|
+
cells.set(cell, value === undefined ? null : value)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const rowKeys = orderKeys(rows, cfg.rowOrder)
|
|
103
|
+
const columnKeys = orderKeys(columns, cfg.columnOrder)
|
|
104
|
+
const labelOf = (key, map, labels) => {
|
|
105
|
+
if (labels && typeof labels === 'object' && typeof labels[key] === 'string') {
|
|
106
|
+
return labels[key]
|
|
107
|
+
}
|
|
108
|
+
return map.get(key).label || key
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
series: rowKeys.map((row) => ({
|
|
112
|
+
key: row,
|
|
113
|
+
name: labelOf(row, rows, cfg.rowLabels),
|
|
114
|
+
data: columnKeys.map((column) => {
|
|
115
|
+
const value = cells.get(`${row}\u0000${column}`)
|
|
116
|
+
return value === undefined ? null : value
|
|
117
|
+
}),
|
|
118
|
+
})),
|
|
119
|
+
categories: columnKeys.map((column) => labelOf(column, columns, cfg.columnLabels)),
|
|
120
|
+
columnKeys,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The cells of a matrix kept inside ONE record: the array at `cellsPath`,
|
|
126
|
+
* narrowed by `cellFilter` (field to value, compared as text), with the axis
|
|
127
|
+
* order read from `rowOrderPath` / `columnOrderPath` on the record when the
|
|
128
|
+
* config gives no `rowOrder` / `columnOrder` list.
|
|
129
|
+
*
|
|
130
|
+
* @param {object|null} record The record that holds the matrix.
|
|
131
|
+
* @param {object} config The `cellSource` config.
|
|
132
|
+
* @param {object} [cellFilter] The `cellFilter`, tokens already resolved.
|
|
133
|
+
* @return {{cells: Array<object>, config: object}} The cells, and the config to lay them out with.
|
|
134
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-one-record
|
|
135
|
+
*/
|
|
136
|
+
export function cellsFromRecord(record, config, cellFilter = {}) {
|
|
137
|
+
const cfg = config && typeof config === 'object' ? config : {}
|
|
138
|
+
const list = record ? readPath(record, cfg.cellsPath) : null
|
|
139
|
+
const wanted = Object.entries(cellFilter && typeof cellFilter === 'object' ? cellFilter : {})
|
|
140
|
+
const cells = (Array.isArray(list) ? list : []).filter((cell) => cell && typeof cell === 'object'
|
|
141
|
+
&& wanted.every(([field, value]) => keyOf(readPath(cell, field)) === keyOf(value)))
|
|
142
|
+
const axis = (path) => {
|
|
143
|
+
const value = record && path ? readPath(record, path) : null
|
|
144
|
+
return Array.isArray(value) ? value : undefined
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
cells,
|
|
148
|
+
config: {
|
|
149
|
+
...cfg,
|
|
150
|
+
rowOrder: Array.isArray(cfg.rowOrder) ? cfg.rowOrder : axis(cfg.rowOrderPath),
|
|
151
|
+
columnOrder: Array.isArray(cfg.columnOrder) ? cfg.columnOrder : axis(cfg.columnOrderPath),
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateOcsUrl } from '@nextcloud/router'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Nextcloud's own directory: groups, the users in a group, and whether a
|
|
10
|
+
* user is in today. Read through the OCS API with the reader's session, so
|
|
11
|
+
* Nextcloud decides what the reader may see (listing a group's members needs
|
|
12
|
+
* an admin or the group's sub-admin).
|
|
13
|
+
*
|
|
14
|
+
* @module utils/nextcloudDirectory
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const OCS_HEADERS = { 'OCS-APIRequest': 'true' }
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The `ocs.data` of an OCS response, or null.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} res An axios response.
|
|
23
|
+
* @return {object|null}
|
|
24
|
+
*/
|
|
25
|
+
function ocsData(res) {
|
|
26
|
+
return (res && res.data && res.data.ocs && res.data.ocs.data) || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The groups, with their member counts.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} axios The axios instance.
|
|
33
|
+
* @param {{search?: string, limit?: number}} [options] Narrowing.
|
|
34
|
+
* @return {Promise<Array<{id: string, name: string, count: number}>>}
|
|
35
|
+
* @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
|
|
36
|
+
*/
|
|
37
|
+
export async function fetchGroups(axios, options = {}) {
|
|
38
|
+
const res = await axios.get(generateOcsUrl('cloud/groups/details'), {
|
|
39
|
+
params: { search: options.search || '', limit: options.limit || 200, format: 'json' },
|
|
40
|
+
headers: OCS_HEADERS,
|
|
41
|
+
})
|
|
42
|
+
const groups = (ocsData(res) && ocsData(res).groups) || []
|
|
43
|
+
return groups.map((group) => ({
|
|
44
|
+
id: String(group.id),
|
|
45
|
+
name: group.displayname || String(group.id),
|
|
46
|
+
count: Number(group.usercount) || 0,
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The members of one group, with their details.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} axios The axios instance.
|
|
54
|
+
* @param {string} groupId The group id.
|
|
55
|
+
* @return {Promise<Array<object>>} Nextcloud's user details, one per member.
|
|
56
|
+
* @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
|
|
57
|
+
*/
|
|
58
|
+
export async function fetchGroupMembers(axios, groupId) {
|
|
59
|
+
const res = await axios.get(generateOcsUrl('cloud/groups/{groupId}/users/details', { groupId }), {
|
|
60
|
+
params: { format: 'json' },
|
|
61
|
+
headers: OCS_HEADERS,
|
|
62
|
+
})
|
|
63
|
+
const users = (ocsData(res) && ocsData(res).users) || {}
|
|
64
|
+
return Array.isArray(users) ? users : Object.values(users)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether a user is in today: an out-of-office period that runs now (the
|
|
69
|
+
* dav app's absence, Nextcloud 28 and later), else their user status.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} axios The axios instance.
|
|
72
|
+
* @param {string} userId The user id.
|
|
73
|
+
* @param {object|null} status The user's entry from the user status list, or null.
|
|
74
|
+
* @return {Promise<{state: string, until: (string|null), message: string}>}
|
|
75
|
+
* `state` is `absent` (with `until`, an ISO date), `away`, `present` or `unknown`.
|
|
76
|
+
* @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
|
|
77
|
+
*/
|
|
78
|
+
export async function fetchPresence(axios, userId, status) {
|
|
79
|
+
try {
|
|
80
|
+
const res = await axios.get(generateOcsUrl('apps/dav/api/v1/outOfOffice/{userId}/now', { userId }), { params: { format: 'json' }, headers: OCS_HEADERS })
|
|
81
|
+
const ooo = ocsData(res)
|
|
82
|
+
if (ooo && ooo.endDate) {
|
|
83
|
+
const end = typeof ooo.endDate === 'number' ? new Date(ooo.endDate * 1000) : new Date(ooo.endDate)
|
|
84
|
+
return { state: 'absent', until: Number.isNaN(end.getTime()) ? null : end.toISOString().slice(0, 10), message: ooo.shortMessage || '' }
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// 404: no absence now, or an instance without the absence API.
|
|
88
|
+
}
|
|
89
|
+
if (status && typeof status === 'object') {
|
|
90
|
+
const message = typeof status.message === 'string' ? status.message : ''
|
|
91
|
+
if (status.status === 'online') {
|
|
92
|
+
return { state: 'present', until: null, message }
|
|
93
|
+
}
|
|
94
|
+
if (status.status === 'away' || status.status === 'dnd') {
|
|
95
|
+
return { state: 'away', until: null, message }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { state: 'unknown', until: null, message: '' }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The user statuses Nextcloud shares with the reader, by user id. Empty when
|
|
103
|
+
* the user status app is off.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} axios The axios instance.
|
|
106
|
+
* @return {Promise<Record<string, object>>}
|
|
107
|
+
* @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
|
|
108
|
+
*/
|
|
109
|
+
export async function fetchStatuses(axios) {
|
|
110
|
+
try {
|
|
111
|
+
const res = await axios.get(generateOcsUrl('apps/user_status/api/v1/statuses'), { params: { limit: 500, format: 'json' }, headers: OCS_HEADERS })
|
|
112
|
+
const list = ocsData(res) || []
|
|
113
|
+
return Object.fromEntries((Array.isArray(list) ? list : []).map((entry) => [entry.userId, entry]))
|
|
114
|
+
} catch {
|
|
115
|
+
return {}
|
|
116
|
+
}
|
|
117
|
+
}
|