@conduction/nextcloud-vue 2.84.0 → 2.85.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/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -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/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
- 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/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -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/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +21 -0
- package/dist/esm/composables/useListView.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 +23 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +41 -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 +742 -397
- package/dist/esm/store/useObjectStore.js +16 -2
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +8 -1
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +278 -0
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
- package/dist/esm/utils/buildManifest.js +4 -1
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/gridStack.js +40 -1
- package/dist/esm/utils/gridStack.js.map +1 -1
- package/dist/esm/utils/humaniseKey.js +41 -0
- package/dist/esm/utils/humaniseKey.js.map +1 -0
- package/dist/esm/utils/notificationRuleLabel.js +6 -23
- package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +8 -2
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +15075 -12590
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +742 -397
- package/l10n/en.json +23 -1
- package/l10n/nl.json +41 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
- package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
- package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
- package/src/composables/useListView.js +21 -0
- package/src/css/board.css +65 -4
- package/src/css/dialog.css +4 -2
- package/src/css/index-page.css +49 -0
- package/src/css/kpi-card.css +13 -0
- package/src/css/look-board.css +96 -7
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +115 -5
- package/src/store/useObjectStore.js +16 -2
- package/src/utils/boardColumns.js +8 -1
- package/src/utils/boardReferenceColumns.js +274 -0
- package/src/utils/buildManifest.js +4 -1
- package/src/utils/gridStack.js +39 -0
- package/src/utils/humaniseKey.js +38 -0
- package/src/utils/notificationRuleLabel.js +5 -23
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +8 -2
|
@@ -26,6 +26,15 @@ function resolveInitialQuickFilterIndex(quickFilters) {
|
|
|
26
26
|
* @param {Array<object>} filterMaps Already route-resolved filter maps.
|
|
27
27
|
* @return {object} The merged filter (scalar when a key has one value, array when many).
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Rows one board fetch asks for. A board shows every matching row at once,
|
|
31
|
+
* so it loads far more than a table page (OpenRegister serves up to 1000);
|
|
32
|
+
* past this the board offers "Load more".
|
|
33
|
+
*
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
36
|
+
export const BOARD_PAGE_SIZE = 500
|
|
37
|
+
|
|
29
38
|
/**
|
|
30
39
|
* The sort a quick filter sets while it is active (`sort: { key, order }`, or a
|
|
31
40
|
* list of those for a multi-column order), as sort keys, or null when it sets
|
|
@@ -236,7 +245,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
|
|
|
236
245
|
defaultSortKeys: initialSortKeys,
|
|
237
246
|
defaultActiveFilters: initialActiveFilters,
|
|
238
247
|
defaultSearchTerm: initialSearchTerm,
|
|
239
|
-
defaultPageSize: (props.pagination && props.pagination.limit) || undefined,
|
|
248
|
+
defaultPageSize: props.viewMode === 'board' ? BOARD_PAGE_SIZE : ((props.pagination && props.pagination.limit) || undefined),
|
|
240
249
|
defaultVisibleColumns: configuredColumnKeys.length ? configuredColumnKeys : null,
|
|
241
250
|
// `pages[].config.extend` → OpenRegister's repeated `_extend[]`. A
|
|
242
251
|
// getter so a reactive change re-scopes the next fetch, matching how
|
|
@@ -339,16 +339,30 @@
|
|
|
339
339
|
{{ stringifyCell(v) }}
|
|
340
340
|
</span>
|
|
341
341
|
</div>
|
|
342
|
-
<!-- Single plain OBJECT → key: value definition list
|
|
342
|
+
<!-- Single plain OBJECT → key: value definition list; a
|
|
343
|
+
nested plain object (e.g. a per-language map) gets
|
|
344
|
+
its own list one level down. -->
|
|
343
345
|
<dl
|
|
344
346
|
v-else-if="fieldValueKind(field) === 'object'"
|
|
345
347
|
class="cn-object-data-widget__deflist">
|
|
346
348
|
<template v-for="(pair, pi) in objectEntries(rawOf(field))" :key="pi">
|
|
347
349
|
<dt>
|
|
348
|
-
{{ pair[0] }}
|
|
350
|
+
{{ nestedLabel(field, [pair[0]]) }}
|
|
349
351
|
</dt>
|
|
350
352
|
<dd>
|
|
351
|
-
|
|
353
|
+
<dl
|
|
354
|
+
v-if="isPlainObject(pair[1])"
|
|
355
|
+
class="cn-object-data-widget__deflist cn-object-data-widget__deflist--nested">
|
|
356
|
+
<template v-for="(sub, si) in objectEntries(pair[1])" :key="si">
|
|
357
|
+
<dt>
|
|
358
|
+
{{ nestedLabel(field, [pair[0], sub[0]]) }}
|
|
359
|
+
</dt>
|
|
360
|
+
<dd>
|
|
361
|
+
<span class="cn-object-data-widget__deflist-text">{{ stringifyNested(sub[1]) }}</span>
|
|
362
|
+
</dd>
|
|
363
|
+
</template>
|
|
364
|
+
</dl>
|
|
365
|
+
<span v-else class="cn-object-data-widget__deflist-text">{{ stringifyNested(pair[1]) }}</span>
|
|
352
366
|
</dd>
|
|
353
367
|
</template>
|
|
354
368
|
</dl>
|
|
@@ -413,6 +427,7 @@ import CnFormDialog from '../CnFormDialog/CnFormDialog.vue'
|
|
|
413
427
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
414
428
|
import { useObjectStore } from '../../store/index.js'
|
|
415
429
|
import { groupDisplayName, loadGroupDisplayName, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
|
|
430
|
+
import { humaniseKey } from '../../utils/humaniseKey.js'
|
|
416
431
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
417
432
|
import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
418
433
|
import { fieldsFromSchema, formatValue } from '../../utils/schema.js'
|
|
@@ -423,6 +438,9 @@ import { CnIcon } from '../CnIcon/index.js'
|
|
|
423
438
|
import { CnObjectMetadataModal } from '../CnObjectMetadataModal/index.js'
|
|
424
439
|
import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
425
440
|
|
|
441
|
+
// Widgets whose value the inline text fallback would corrupt.
|
|
442
|
+
const STRUCTURED_WIDGETS = ['json', 'code', 'file', 'sub-objects', 'child-records']
|
|
443
|
+
|
|
426
444
|
/**
|
|
427
445
|
* CnObjectDataWidget — Schema-driven editable data grid widget.
|
|
428
446
|
*
|
|
@@ -1363,6 +1381,59 @@ export default {
|
|
|
1363
1381
|
return (raw && typeof raw === 'object') ? Object.entries(raw) : []
|
|
1364
1382
|
},
|
|
1365
1383
|
|
|
1384
|
+
/**
|
|
1385
|
+
* Whether a value is a plain object (not an array, not null).
|
|
1386
|
+
*
|
|
1387
|
+
* @param {unknown} v The value.
|
|
1388
|
+
* @return {boolean} True for a plain object.
|
|
1389
|
+
*/
|
|
1390
|
+
isPlainObject(v) {
|
|
1391
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
1392
|
+
},
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Label for a key inside an object value: the sub-schema's `title`
|
|
1396
|
+
* (translated) when the schema describes it, else the key, made
|
|
1397
|
+
* readable when it is identifier-shaped (`shortDescription` → "Short
|
|
1398
|
+
* description"; `nl` stays `nl`).
|
|
1399
|
+
*
|
|
1400
|
+
* @param {object} field Resolved field definition.
|
|
1401
|
+
* @param {string[]} path Keys from the field's value down to this entry.
|
|
1402
|
+
* @return {string} The label.
|
|
1403
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
|
|
1404
|
+
*/
|
|
1405
|
+
nestedLabel(field, path) {
|
|
1406
|
+
let prop = ((this.schema && this.schema.properties) || {})[field.key]
|
|
1407
|
+
for (const key of path) {
|
|
1408
|
+
if (!this.isPlainObject(prop)) {
|
|
1409
|
+
prop = null
|
|
1410
|
+
break
|
|
1411
|
+
}
|
|
1412
|
+
prop = (prop.properties && prop.properties[key])
|
|
1413
|
+
|| (this.isPlainObject(prop.additionalProperties) ? prop.additionalProperties : null)
|
|
1414
|
+
}
|
|
1415
|
+
if (prop && typeof prop.title === 'string' && prop.title) {
|
|
1416
|
+
return this.cnTranslate(prop.title)
|
|
1417
|
+
}
|
|
1418
|
+
const key = String(path[path.length - 1])
|
|
1419
|
+
return /[A-Z_-]/.test(key) ? humaniseKey(key) : key
|
|
1420
|
+
},
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Stringify a value inside a definition list: a list of scalars reads
|
|
1424
|
+
* as a comma-separated line, anything else as `stringifyCell`.
|
|
1425
|
+
*
|
|
1426
|
+
* @param {unknown} v The value.
|
|
1427
|
+
* @return {string} The display string.
|
|
1428
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
|
|
1429
|
+
*/
|
|
1430
|
+
stringifyNested(v) {
|
|
1431
|
+
if (Array.isArray(v) && v.length && v.every((item) => item === null || typeof item !== 'object')) {
|
|
1432
|
+
return v.map((item) => this.stringifyCell(item)).join(', ')
|
|
1433
|
+
}
|
|
1434
|
+
return this.stringifyCell(v)
|
|
1435
|
+
},
|
|
1436
|
+
|
|
1366
1437
|
/**
|
|
1367
1438
|
* Stringify a scalar cell value; a nested object/array collapses to
|
|
1368
1439
|
* compact JSON (never "[object Object]").
|
|
@@ -1690,15 +1761,53 @@ export default {
|
|
|
1690
1761
|
}))
|
|
1691
1762
|
},
|
|
1692
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* Whether a field's value cannot be edited inline: a structured widget
|
|
1766
|
+
* (json, code, file, sub-objects, child-records), or a plain-object /
|
|
1767
|
+
* list-of-objects value that is not a relation. Scalar widgets without
|
|
1768
|
+
* a dedicated editor (duration, language, …) keep the text fallback.
|
|
1769
|
+
*
|
|
1770
|
+
* @param {object} field Resolved field definition.
|
|
1771
|
+
* @return {boolean} True when only the form dialog can edit it.
|
|
1772
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
|
|
1773
|
+
*/
|
|
1774
|
+
hasNoInlineEditor(field) {
|
|
1775
|
+
if (STRUCTURED_WIDGETS.includes(field.widget)) {
|
|
1776
|
+
return true
|
|
1777
|
+
}
|
|
1778
|
+
const prop = ((this.schema && this.schema.properties) || {})[field.key]
|
|
1779
|
+
if (this.isRelationField(prop)) {
|
|
1780
|
+
return false
|
|
1781
|
+
}
|
|
1782
|
+
// A list of objects by schema, also while empty (it resolves to `tags`).
|
|
1783
|
+
if (prop && prop.type === 'array' && prop.items && prop.items.type === 'object') {
|
|
1784
|
+
return true
|
|
1785
|
+
}
|
|
1786
|
+
// Classify the raw value itself: fieldValueKind reports a formatter
|
|
1787
|
+
// field as 'scalar' whatever it holds.
|
|
1788
|
+
const raw = this.rawOf(field)
|
|
1789
|
+
if (Array.isArray(raw)) {
|
|
1790
|
+
return raw.some((v) => this.isPlainObject(v))
|
|
1791
|
+
}
|
|
1792
|
+
return this.isPlainObject(raw)
|
|
1793
|
+
},
|
|
1794
|
+
|
|
1693
1795
|
/**
|
|
1694
1796
|
* Check if a field is editable.
|
|
1695
1797
|
*
|
|
1696
1798
|
* @param {object} field - Resolved field definition from resolvedFields
|
|
1799
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
|
|
1697
1800
|
*/
|
|
1698
1801
|
isEditable(field) {
|
|
1699
1802
|
if (!this.editable) {
|
|
1700
1803
|
return false
|
|
1701
1804
|
}
|
|
1805
|
+
// No inline editor exists for structured values: the text fallback
|
|
1806
|
+
// would commit "[object Object]". Those edit through the form dialog,
|
|
1807
|
+
// unless the consumer supplies its own `field-<key>` editor slot.
|
|
1808
|
+
if (!this.$slots['field-' + field.key] && this.hasNoInlineEditor(field)) {
|
|
1809
|
+
return false
|
|
1810
|
+
}
|
|
1702
1811
|
// Per-field override takes priority
|
|
1703
1812
|
const override = this.resolvedOverrides[field.key]
|
|
1704
1813
|
if (override && typeof override.editable === 'boolean') {
|
|
@@ -2439,6 +2548,17 @@ export default {
|
|
|
2439
2548
|
overflow-wrap: anywhere;
|
|
2440
2549
|
}
|
|
2441
2550
|
|
|
2551
|
+
/* A long description keeps its own paragraphs. Set on an inline span with no
|
|
2552
|
+
surrounding template whitespace, so only the value's own line breaks show. */
|
|
2553
|
+
.cn-object-data-widget__deflist-text {
|
|
2554
|
+
white-space: pre-wrap;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
.cn-object-data-widget__deflist--nested {
|
|
2558
|
+
padding-inline-start: 8px;
|
|
2559
|
+
border-inline-start: 2px solid var(--color-border);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2442
2562
|
/* Responsive: collapse to single column on narrow widths */
|
|
2443
2563
|
@media (max-width: 600px) {
|
|
2444
2564
|
.cn-object-data-widget__grid {
|
|
@@ -141,9 +141,11 @@
|
|
|
141
141
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
142
142
|
import { translate as t } from '@nextcloud/l10n'
|
|
143
143
|
import { NcLoadingIcon } from '@nextcloud/vue'
|
|
144
|
+
import { inject, ref } from 'vue'
|
|
144
145
|
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
145
146
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
146
147
|
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
148
|
+
import { useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
147
149
|
import { actionLink, dispatchAction, interpolateActionTarget } from '../../utils/actionsDispatcher.js'
|
|
148
150
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
149
151
|
import { ageInDays } from '../../utils/boardDate.js'
|
|
@@ -180,7 +182,8 @@ function unwrap(value) {
|
|
|
180
182
|
* objects filtered on a reference to this record. The source keys are those of
|
|
181
183
|
* `object-list` (`register`, `schema`, `filter` with the `@objectId` /
|
|
182
184
|
* `@object.<field>` / `@workspace.<key>` tokens, `sort`, `limit`, `extend`,
|
|
183
|
-
* `rowRoute`, `pageControls`)
|
|
185
|
+
* `rowRoute`, `pageControls`), or `endpointSource` (an app endpoint whose
|
|
186
|
+
* payload, after `responsePath`, is the array of rows, as on the object table); `avatar` puts a circle with the initials of a name
|
|
184
187
|
* template before the title; what differs is the drawing: `presentation` is `rows`, `feed`
|
|
185
188
|
* or `inline`, and each line is a template (`"{identifier} · {caseTypeName}"`,
|
|
186
189
|
* `"Termijn {deadline|date}"`).
|
|
@@ -213,7 +216,9 @@ export default {
|
|
|
213
216
|
* The list config: `{ register, schema, filter?, sort?, limit?, extend?,
|
|
214
217
|
* presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
215
218
|
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
|
|
216
|
-
* valueLabels? }`. `
|
|
219
|
+
* valueLabels?, endpointSource? }`. `endpointSource` (`{ url, method?,
|
|
220
|
+
* params?, responsePath? }`) reads the rows from an app endpoint instead
|
|
221
|
+
* of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
|
|
217
222
|
* the templates show.
|
|
218
223
|
*
|
|
219
224
|
* @type {object}
|
|
@@ -238,6 +243,28 @@ export default {
|
|
|
238
243
|
|
|
239
244
|
emits: ['header'],
|
|
240
245
|
|
|
246
|
+
setup(props) {
|
|
247
|
+
const objectCtxRaw = inject('cnObjectContext', null)
|
|
248
|
+
const workspaceRaw = inject('cnWorkspaceContext', null)
|
|
249
|
+
const appConfigRaw = inject('cnAppConfig', ref({}))
|
|
250
|
+
const { data, loading, error } = useEndpointSource(
|
|
251
|
+
() => props.content?.endpointSource || null,
|
|
252
|
+
{
|
|
253
|
+
ctx: () => {
|
|
254
|
+
const injected = unwrap(objectCtxRaw) || {}
|
|
255
|
+
return {
|
|
256
|
+
...injected,
|
|
257
|
+
objectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,
|
|
258
|
+
object: props.objectData || injected.object || null,
|
|
259
|
+
workspace: unwrap(workspaceRaw) || {},
|
|
260
|
+
config: unwrapAppConfig(appConfigRaw) || {},
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
)
|
|
265
|
+
return { epData: data, epLoading: loading, epError: error }
|
|
266
|
+
},
|
|
267
|
+
|
|
241
268
|
data() {
|
|
242
269
|
return {
|
|
243
270
|
rows: [],
|
|
@@ -294,6 +321,22 @@ export default {
|
|
|
294
321
|
return { config: unwrapAppConfig(this.cnAppConfig) }
|
|
295
322
|
},
|
|
296
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Whether the rows come from `endpointSource` rather than OpenRegister.
|
|
326
|
+
*
|
|
327
|
+
* @return {boolean}
|
|
328
|
+
* @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
|
|
329
|
+
*/
|
|
330
|
+
endpointActive() {
|
|
331
|
+
const source = this.content?.endpointSource
|
|
332
|
+
return Boolean(source && typeof source === 'object' && source.url)
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/** @return {string} The endpoint read's state, to copy into the list when it changes. */
|
|
336
|
+
endpointKey() {
|
|
337
|
+
return this.endpointActive ? JSON.stringify([this.epData, this.epLoading, this.epError]) : ''
|
|
338
|
+
},
|
|
339
|
+
|
|
297
340
|
/** @return {number} The fetch cap. */
|
|
298
341
|
limit() {
|
|
299
342
|
const value = Number(this.content?.limit)
|
|
@@ -398,6 +441,10 @@ export default {
|
|
|
398
441
|
requestKey() {
|
|
399
442
|
this.fetchRows()
|
|
400
443
|
},
|
|
444
|
+
|
|
445
|
+
endpointKey() {
|
|
446
|
+
this.fetchRows()
|
|
447
|
+
},
|
|
401
448
|
},
|
|
402
449
|
|
|
403
450
|
mounted() {
|
|
@@ -854,6 +901,23 @@ export default {
|
|
|
854
901
|
return { rows, total: typeof head.total === 'number' ? head.total : rows.length }
|
|
855
902
|
},
|
|
856
903
|
|
|
904
|
+
/**
|
|
905
|
+
* Copy the endpoint read into the list: its payload must be an array of
|
|
906
|
+
* rows (anything else is an empty list), and a failed read is an error.
|
|
907
|
+
*
|
|
908
|
+
* @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
|
|
909
|
+
*/
|
|
910
|
+
takeEndpointRows() {
|
|
911
|
+
this.rows = Array.isArray(this.epData) ? this.epData : []
|
|
912
|
+
this.total = this.rows.length
|
|
913
|
+
this.loading = this.epLoading === true
|
|
914
|
+
this.error = Boolean(this.epError)
|
|
915
|
+
this.lenses = {}
|
|
916
|
+
if (!this.loading) {
|
|
917
|
+
this.emitHeader()
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
|
|
857
921
|
/**
|
|
858
922
|
* Fetch the list. Sends nothing while a required token is unresolved
|
|
859
923
|
* or the source is incomplete.
|
|
@@ -862,6 +926,10 @@ export default {
|
|
|
862
926
|
*/
|
|
863
927
|
async fetchRows() {
|
|
864
928
|
const c = this.content || {}
|
|
929
|
+
if (this.endpointActive) {
|
|
930
|
+
this.takeEndpointRows()
|
|
931
|
+
return
|
|
932
|
+
}
|
|
865
933
|
if (!c.register || !c.schema || this.waiting) {
|
|
866
934
|
this.rows = []
|
|
867
935
|
this.total = 0
|
|
@@ -111,6 +111,10 @@ export default {
|
|
|
111
111
|
</script>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Several groups
|
|
115
|
+
|
|
116
|
+
With more than one group each group starts closed: its heading is a button that shows the group name and how many of its types are configured ("2/8 configured"), and opens the register select and type list on a click or Enter. A page with several registers is a short card until the administrator opens the group they need.
|
|
117
|
+
|
|
114
118
|
## Additional props
|
|
115
119
|
|
|
116
120
|
| Prop | Type | Default | Description |
|
|
@@ -122,5 +126,6 @@ export default {
|
|
|
122
126
|
| `reimporting` | Boolean | `false` | Disables the reimport button and shows a spinner while reimport is in progress |
|
|
123
127
|
| `saveButtonText` | String | `'Save configuration'` | Label for the save button |
|
|
124
128
|
| `reimportButtonText` | String | `'Re-import configuration'` | Label for the reimport button |
|
|
129
|
+
| `collapseGroups` | Boolean | `null` | Draw each group as a heading button that opens and closes it, with its configured count shown while closed; the groups start closed. `null` does this when there is more than one group, `true` or `false` forces it |
|
|
125
130
|
| `autoMatch` | Boolean | `true` | Automatically match schema titles to type slugs when a register is selected |
|
|
126
131
|
| `labels` | Object | *(see below)* | UI label overrides for all user-visible strings (register, schema, notConfigured, noSchemas, selectRegister, selectSchema, allConfigured, partiallyConfigured) |
|