@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.31
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.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +85 -6
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +4 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +40 -3
- 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 +19 -11
- 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 +43 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +125 -0
- 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/CnFieldListWidget/CnFieldListWidget.vue2.js +3 -2
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -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/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +13 -6
- 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/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- 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/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +94 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +10 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +8 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -8
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +1 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +19 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.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/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4309 -2233
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -8
- package/l10n/en.json +8 -1
- package/l10n/nl.json +54 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +112 -7
- package/src/components/CnCellRenderer/CnCellRenderer.vue +4 -1
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +40 -3
- package/src/components/CnDataTable/CnDataTable.vue +19 -11
- package/src/components/CnDetailPage/CnDetailPage.vue +44 -1
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +125 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +3 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -3
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +13 -6
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +281 -10
- package/src/components/index.js +8 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-index.css +16 -8
- package/src/index.js +8 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/facets.js +74 -2
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -1765,17 +1765,6 @@ export default {
|
|
|
1765
1765
|
return (col && typeof col.format === 'object') ? col.format : null
|
|
1766
1766
|
},
|
|
1767
1767
|
|
|
1768
|
-
/**
|
|
1769
|
-
* Value to render in a cell. For a column that declares `aggregate`
|
|
1770
|
-
* (a count of related objects), returns the cached count once
|
|
1771
|
-
* `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
|
|
1772
|
-
* if it failed / there's nothing to count). Otherwise the row's
|
|
1773
|
-
* property value via `getCellValue`.
|
|
1774
|
-
*
|
|
1775
|
-
* @param {object} row The row data.
|
|
1776
|
-
* @param {object} col The column definition.
|
|
1777
|
-
* @return {unknown} The value handed to the slot / CnCellRenderer.
|
|
1778
|
-
*/
|
|
1779
1768
|
/**
|
|
1780
1769
|
* The secondary line of a cell, from `col.secondary`: a function is
|
|
1781
1770
|
* called with the row; a string with `{field}` placeholders is a
|
|
@@ -1806,6 +1795,21 @@ export default {
|
|
|
1806
1795
|
return asText(this.getCellValue(row, spec))
|
|
1807
1796
|
},
|
|
1808
1797
|
|
|
1798
|
+
/**
|
|
1799
|
+
* Value to render in a cell. For a column that declares `aggregate`
|
|
1800
|
+
* (a count of related objects), returns the cached count once
|
|
1801
|
+
* `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
|
|
1802
|
+
* if it failed / there's nothing to count). A count of zero reads as
|
|
1803
|
+
* `aggregate.zeroLabel` when the column sets one ("None", which the
|
|
1804
|
+
* app's `cnTranslate` turns into "Geen" as the boards draw it); without
|
|
1805
|
+
* it the cell shows `0` as before. Otherwise the row's property value
|
|
1806
|
+
* via `getCellValue`.
|
|
1807
|
+
*
|
|
1808
|
+
* @spec openspec/changes/aggregate-zero-label/specs/aggregate-zero-label/spec.md#requirement-a-count-column-can-name-zero
|
|
1809
|
+
* @param {object} row The row data.
|
|
1810
|
+
* @param {object} col The column definition.
|
|
1811
|
+
* @return {unknown} The value handed to the slot / CnCellRenderer.
|
|
1812
|
+
*/
|
|
1809
1813
|
cellValue(row, col) {
|
|
1810
1814
|
if (col && col.aggregate) {
|
|
1811
1815
|
const cached = this.aggregateValues[String(row[this.rowKey])]
|
|
@@ -1813,6 +1817,10 @@ export default {
|
|
|
1813
1817
|
if (v === undefined) {
|
|
1814
1818
|
return this.aggregateLoading ? '…' : '—'
|
|
1815
1819
|
}
|
|
1820
|
+
const zeroLabel = col.aggregate.zeroLabel
|
|
1821
|
+
if ((v === 0 || v === '0') && typeof zeroLabel === 'string' && zeroLabel !== '') {
|
|
1822
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(zeroLabel) : zeroLabel
|
|
1823
|
+
}
|
|
1816
1824
|
return v
|
|
1817
1825
|
}
|
|
1818
1826
|
return this.getCellValue(row, col.key)
|
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
:layout="section.layout"
|
|
746
746
|
:editable="section.editable"
|
|
747
747
|
:columns="12"
|
|
748
|
-
:columnOpts="
|
|
748
|
+
:columnOpts="bodyColumnOpts"
|
|
749
749
|
class="cn-detail-page__grid"
|
|
750
750
|
@layoutChange="section.view ? onViewLayoutChange($event) : onBodyLayoutChange($event)">
|
|
751
751
|
<template #widget="{ item }">
|
|
@@ -1295,6 +1295,35 @@ const DEFAULT_DETAIL_COLUMN_OPTS = Object.freeze({
|
|
|
1295
1295
|
breakpointForWindow: false,
|
|
1296
1296
|
})
|
|
1297
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* Where the default table stacks when a side column sits beside the body grid.
|
|
1300
|
+
*
|
|
1301
|
+
* The side column takes about 320px, so the grid it leaves is far narrower than
|
|
1302
|
+
* the page: 790px at a 1100px window. Stacking that at 1000px made a row of
|
|
1303
|
+
* small tiles (a KPI row of 5 or 6 stat tiles) impossible beside a side column.
|
|
1304
|
+
* 1000px exists to keep a `gridWidth: 3` side PANEL inside the grid readable; a
|
|
1305
|
+
* page with a real side column has no such panel, and 12 columns at 600px is
|
|
1306
|
+
* still 50px a column, enough for a tile. Below 600px it stacks as before.
|
|
1307
|
+
*/
|
|
1308
|
+
const SIDE_COLUMN_STACK_WIDTH = 600
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Whether `opts` is the untouched default table, so a page that passed its own
|
|
1312
|
+
* `columnOpts` keeps exactly what it asked for.
|
|
1313
|
+
*
|
|
1314
|
+
* @param {object|null} opts The `columnOpts` value.
|
|
1315
|
+
* @return {boolean}
|
|
1316
|
+
*/
|
|
1317
|
+
function isDefaultColumnOpts(opts) {
|
|
1318
|
+
const breakpoints = opts && opts.breakpoints
|
|
1319
|
+
return !!opts
|
|
1320
|
+
&& opts.columnMax === DEFAULT_DETAIL_COLUMN_OPTS.columnMax
|
|
1321
|
+
&& Array.isArray(breakpoints)
|
|
1322
|
+
&& breakpoints.length === 1
|
|
1323
|
+
&& breakpoints[0].w === DEFAULT_DETAIL_COLUMN_OPTS.breakpoints[0].w
|
|
1324
|
+
&& breakpoints[0].c === 1
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1298
1327
|
/**
|
|
1299
1328
|
* Event-bus channel a page-level refresh is announced on. The page's own
|
|
1300
1329
|
* Actions menu emits it, the manifest `refresh` / `api-call` actions bump it,
|
|
@@ -3335,6 +3364,20 @@ export default {
|
|
|
3335
3364
|
.map(({ widget }) => widget)
|
|
3336
3365
|
},
|
|
3337
3366
|
|
|
3367
|
+
/**
|
|
3368
|
+
* The `columnOpts` the body grid gets: the default table stacks at 1000px
|
|
3369
|
+
* of grid width, or at 600px when a side column narrows the grid. An
|
|
3370
|
+
* explicit `columnOpts` prop is passed through untouched.
|
|
3371
|
+
*
|
|
3372
|
+
* @return {object|null} The column options.
|
|
3373
|
+
*/
|
|
3374
|
+
bodyColumnOpts() {
|
|
3375
|
+
if (!this.showsSideColumn || !isDefaultColumnOpts(this.columnOpts)) {
|
|
3376
|
+
return this.columnOpts
|
|
3377
|
+
}
|
|
3378
|
+
return { ...this.columnOpts, breakpoints: [{ w: SIDE_COLUMN_STACK_WIDTH, c: 1 }] }
|
|
3379
|
+
},
|
|
3380
|
+
|
|
3338
3381
|
/**
|
|
3339
3382
|
* Whether the side column renders.
|
|
3340
3383
|
*
|
|
@@ -31,7 +31,7 @@ import { readPath } from '../../utils/readPath.js'
|
|
|
31
31
|
* Renders CnDocumentReviewList from a list on the bound record:
|
|
32
32
|
* `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.
|
|
33
33
|
* A row action with a `route` opens that route with the row's key as `id`.
|
|
34
|
-
* Resolved by its registry key,
|
|
34
|
+
* Resolved by its registry key, and exported for an app that renders it itself.
|
|
35
35
|
*/
|
|
36
36
|
export default {
|
|
37
37
|
name: 'CnDocumentReviewWidget',
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<script>
|
|
63
63
|
import { translate as t } from '@nextcloud/l10n'
|
|
64
64
|
import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'
|
|
65
|
-
import {
|
|
65
|
+
import { facetOptions } from '../../utils/facets.js'
|
|
66
66
|
import { filtersFromSchema } from '../../utils/schema.js'
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -175,12 +175,12 @@ export default {
|
|
|
175
175
|
|
|
176
176
|
getFilterOptions(filter) {
|
|
177
177
|
// Use facet data if available (live values with counts)
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
const live = facetOptions(
|
|
179
|
+
this.facetData[filter.key],
|
|
180
|
+
(count) => t('nextcloud-vue', 'No value ({count})', { count }),
|
|
181
|
+
)
|
|
182
|
+
if (live !== null) {
|
|
183
|
+
return live
|
|
184
184
|
}
|
|
185
185
|
// Fall back to static enum options from schema
|
|
186
186
|
return filter.options || []
|
|
@@ -140,6 +140,7 @@ import EyeOutline from 'vue-material-design-icons/EyeOutline.vue'
|
|
|
140
140
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
141
141
|
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
142
142
|
import { resolveEndpointRequest } from '../../composables/useEndpointSource.js'
|
|
143
|
+
import { useRefLabels } from '../../composables/useRefLabels.js'
|
|
143
144
|
import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
144
145
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
145
146
|
import { formatBoardDate } from '../../utils/boardDate.js'
|
|
@@ -150,6 +151,29 @@ import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveF
|
|
|
150
151
|
/** Kinds of entry a `fields[]` item can be. A labelled item without `as` is a row. */
|
|
151
152
|
const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text'])
|
|
152
153
|
|
|
154
|
+
/**
|
|
155
|
+
* The label resolver every facts widget on the page shares, made on first use
|
|
156
|
+
* (the object store needs an active pinia). One batched request per register and
|
|
157
|
+
* schema for the ids it has not seen; a label it has read stays until the page
|
|
158
|
+
* reloads, and an object the store already holds is not fetched at all.
|
|
159
|
+
*
|
|
160
|
+
* @type {{resolve: Function}|null}
|
|
161
|
+
*/
|
|
162
|
+
let refLabels = null
|
|
163
|
+
|
|
164
|
+
/** The page-wide reference label resolver. */
|
|
165
|
+
function sharedRefLabels() {
|
|
166
|
+
if (!refLabels) {
|
|
167
|
+
refLabels = useRefLabels()
|
|
168
|
+
}
|
|
169
|
+
return refLabels
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Key of a resolved reference in the widget's `labels` data. */
|
|
173
|
+
function refKey(resolve, id) {
|
|
174
|
+
return `${resolve.register}/${resolve.schema}/${resolve.labelField || ''}/${id}`
|
|
175
|
+
}
|
|
176
|
+
|
|
153
177
|
/** Event-bus channel a reveal of a masked value is announced on, so an app can log it. */
|
|
154
178
|
export const FACTS_REVEAL_CHANNEL = 'cn:facts:reveal'
|
|
155
179
|
|
|
@@ -213,6 +237,7 @@ export default {
|
|
|
213
237
|
* `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `pill`.
|
|
214
238
|
* A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
|
|
215
239
|
* A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
|
|
240
|
+
* `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read.
|
|
216
241
|
* - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
|
|
217
242
|
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year) or `list` (an array joined by ", ", each item read at `itemPath`).
|
|
218
243
|
* `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
|
|
@@ -255,6 +280,8 @@ export default {
|
|
|
255
280
|
revealed: {},
|
|
256
281
|
/** Key of the row whose value was just copied. */
|
|
257
282
|
copied: '',
|
|
283
|
+
/** Labels of the referenced objects read so far, by `refKey`. */
|
|
284
|
+
labels: {},
|
|
258
285
|
}
|
|
259
286
|
},
|
|
260
287
|
|
|
@@ -430,6 +457,13 @@ export default {
|
|
|
430
457
|
},
|
|
431
458
|
},
|
|
432
459
|
|
|
460
|
+
record: {
|
|
461
|
+
immediate: true,
|
|
462
|
+
handler() {
|
|
463
|
+
this.loadLabels()
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
|
|
433
467
|
headerAction: {
|
|
434
468
|
immediate: true,
|
|
435
469
|
handler() {
|
|
@@ -668,6 +702,94 @@ export default {
|
|
|
668
702
|
})
|
|
669
703
|
},
|
|
670
704
|
|
|
705
|
+
/**
|
|
706
|
+
* The text of a reference: the labels of the objects its id(s) name, joined
|
|
707
|
+
* by ", ". An id whose label is not known yet (or cannot be read) adds nothing.
|
|
708
|
+
*
|
|
709
|
+
* @param {unknown} raw The id, or a list of ids (or of `{ id }` objects).
|
|
710
|
+
* @param {object} spec `{ resolve: { register, schema, labelField? }, itemPath? }`.
|
|
711
|
+
* @return {string}
|
|
712
|
+
*/
|
|
713
|
+
refText(raw, spec) {
|
|
714
|
+
return this.refIds(raw, spec)
|
|
715
|
+
.map((id) => this.labels[refKey(spec.resolve, id)])
|
|
716
|
+
.filter((label) => typeof label === 'string' && label !== '')
|
|
717
|
+
.join(', ')
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* The ids a reference value holds.
|
|
722
|
+
*
|
|
723
|
+
* @param {unknown} raw The value.
|
|
724
|
+
* @param {object} spec `{ itemPath? }`.
|
|
725
|
+
* @return {string[]}
|
|
726
|
+
*/
|
|
727
|
+
refIds(raw, spec) {
|
|
728
|
+
return (Array.isArray(raw) ? raw : [raw])
|
|
729
|
+
.map((item) => (item && typeof item === 'object' ? (spec.itemPath ? readPath(item, spec.itemPath) : (item.id ?? item.uuid)) : item))
|
|
730
|
+
.filter((id) => id !== null && id !== undefined && id !== '' && typeof id !== 'object')
|
|
731
|
+
.map(String)
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Every entry or part that carries `resolve`, with the record's value at its path.
|
|
736
|
+
*
|
|
737
|
+
* @param {object} record The object.
|
|
738
|
+
* @return {Array<{spec: object, raw: unknown}>}
|
|
739
|
+
*/
|
|
740
|
+
refSpecs(record) {
|
|
741
|
+
const fields = Array.isArray(this.content?.fields) ? this.content.fields : []
|
|
742
|
+
const specs = []
|
|
743
|
+
const visit = (spec) => {
|
|
744
|
+
if (!spec || typeof spec !== 'object') {
|
|
745
|
+
return
|
|
746
|
+
}
|
|
747
|
+
if (spec.resolve && typeof spec.resolve === 'object' && spec.path) {
|
|
748
|
+
specs.push({ spec, raw: readPath(record, spec.path) })
|
|
749
|
+
}
|
|
750
|
+
const nested = [...(Array.isArray(spec.parts) ? spec.parts : []), ...(Array.isArray(spec.title) ? spec.title : [spec.title]), ...(Array.isArray(spec.subtitle) ? spec.subtitle : [spec.subtitle])]
|
|
751
|
+
nested.forEach(visit)
|
|
752
|
+
}
|
|
753
|
+
fields.forEach(visit)
|
|
754
|
+
return specs
|
|
755
|
+
},
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Read the labels of the referenced objects, one batched request per
|
|
759
|
+
* register and schema, and keep them in `labels`.
|
|
760
|
+
*
|
|
761
|
+
* @param {object|null} record The object shown (defaults to the current record).
|
|
762
|
+
* @return {Promise<void>}
|
|
763
|
+
*/
|
|
764
|
+
async loadLabels(record = this.record) {
|
|
765
|
+
if (!record) {
|
|
766
|
+
return
|
|
767
|
+
}
|
|
768
|
+
const groups = new Map()
|
|
769
|
+
for (const { spec, raw } of this.refSpecs(record)) {
|
|
770
|
+
const { register, schema, labelField } = spec.resolve
|
|
771
|
+
if (!register || !schema) {
|
|
772
|
+
continue
|
|
773
|
+
}
|
|
774
|
+
const groupKey = `${register}/${schema}/${labelField || ''}`
|
|
775
|
+
const group = groups.get(groupKey) || { resolve: spec.resolve, ids: new Set() }
|
|
776
|
+
this.refIds(raw, spec).forEach((id) => group.ids.add(id))
|
|
777
|
+
groups.set(groupKey, group)
|
|
778
|
+
}
|
|
779
|
+
if (groups.size === 0) {
|
|
780
|
+
return
|
|
781
|
+
}
|
|
782
|
+
const resolver = sharedRefLabels()
|
|
783
|
+
const next = { ...this.labels }
|
|
784
|
+
await Promise.all([...groups.values()].map(async ({ resolve, ids }) => {
|
|
785
|
+
const found = await resolver.resolve(resolve.register, resolve.schema, [...ids], resolve.labelField)
|
|
786
|
+
Object.entries(found).forEach(([id, label]) => {
|
|
787
|
+
next[refKey(resolve, id)] = label
|
|
788
|
+
})
|
|
789
|
+
}))
|
|
790
|
+
this.labels = next
|
|
791
|
+
},
|
|
792
|
+
|
|
671
793
|
/**
|
|
672
794
|
* Read the source object.
|
|
673
795
|
*
|
|
@@ -774,6 +896,9 @@ export default {
|
|
|
774
896
|
if (raw === null || raw === undefined || raw === '') {
|
|
775
897
|
return ''
|
|
776
898
|
}
|
|
899
|
+
if (spec.resolve && typeof spec.resolve === 'object') {
|
|
900
|
+
return this.refText(raw, spec)
|
|
901
|
+
}
|
|
777
902
|
const format = spec.format || 'text'
|
|
778
903
|
if (format === 'list' || Array.isArray(raw)) {
|
|
779
904
|
const items = (Array.isArray(raw) ? raw : [raw])
|
|
@@ -137,7 +137,7 @@ export default {
|
|
|
137
137
|
*
|
|
138
138
|
* - `field` (required) — the object field holding the array.
|
|
139
139
|
* - `key` — the item field that identifies an entry (default: the item itself for strings).
|
|
140
|
-
* - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix? }, detail?: { template, when? } }`.
|
|
140
|
+
* - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix?, fallback? }, detail?: { template, when? } }`.
|
|
141
141
|
* - `add` — `{ label?, fields: [{ key, label, type?: 'text'|'select', options?, required?, pattern?, patternMessage? }], unique?, defaults? }`.
|
|
142
142
|
* - `remove` — `{ label? }`; omitted = no remove.
|
|
143
143
|
* - `rowActions` — `[{ id, label, when?, request: { method?, url, payload? } }]`; tokens `@object.<field>`, `@objectId`, `@row.<field>`.
|
|
@@ -375,7 +375,8 @@ export default {
|
|
|
375
375
|
asideOf(entry, spec) {
|
|
376
376
|
const value = this.text(entry, spec)
|
|
377
377
|
if (value === '') {
|
|
378
|
-
|
|
378
|
+
// No value: the optional `fallback` label, translated, instead of an empty aside.
|
|
379
|
+
return this.tr(spec.fallback)
|
|
379
380
|
}
|
|
380
381
|
return spec.prefix ? `${this.tr(spec.prefix)} ${value}` : value
|
|
381
382
|
},
|