@conduction/nextcloud-vue 2.110.0 → 2.112.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/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +2 -1
- 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/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- 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/CnDetailPage/CnDetailPage.vue2.js +52 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +98 -63
- 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 +48 -11
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +203 -14
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
- package/dist/esm/composables/useRefLabels.js +26 -6
- package/dist/esm/composables/useRefLabels.js.map +1 -1
- package/dist/esm/integrations/registry.js +4 -0
- package/dist/esm/integrations/registry.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -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 +630 -303
- package/dist/esm/store/plugins/liveUpdates.js +3 -1
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/plugins/logs.js +2 -1
- package/dist/esm/store/plugins/logs.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +28 -1
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +13 -5
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerFieldChips.js +21 -2
- package/dist/esm/utils/headerFieldChips.js.map +1 -1
- package/dist/esm/utils/mapList.js +26 -11
- package/dist/esm/utils/mapList.js.map +1 -1
- package/dist/esm/utils/savedViewHelpers.js +40 -3
- package/dist/esm/utils/savedViewHelpers.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +12 -5
- package/dist/esm/utils/savedViewMenu.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/nextcloud-vue.cjs.js +2760 -1995
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +630 -303
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
- package/src/components/CnAppRoot/CnAppRoot.vue +2 -1
- package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
- package/src/components/CnBoardView/CnBoardView.vue +58 -5
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +77 -20
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.md +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +211 -16
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +2 -2
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
- package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
- package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
- package/src/composables/useRefLabels.js +25 -5
- package/src/css/board.css +5 -2
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +55 -22
- package/src/css/look-board-index.css +82 -12
- package/src/css/look-board-reports.css +1 -1
- package/src/css/look-board.css +84 -11
- package/src/integrations/registry.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +11 -5
- package/src/store/plugins/liveUpdates.js +3 -1
- package/src/store/plugins/logs.js +2 -1
- package/src/utils/detailActionModel.js +27 -0
- package/src/utils/fillTemplate.js +13 -5
- package/src/utils/headerFieldChips.js +21 -2
- package/src/utils/mapList.js +26 -11
- package/src/utils/savedViewHelpers.js +39 -2
- package/src/utils/savedViewMenu.js +12 -5
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -147,6 +147,39 @@
|
|
|
147
147
|
hidden
|
|
148
148
|
data-testid="cn-detail-widget-props-pending" />
|
|
149
149
|
|
|
150
|
+
<!-- A mount-mode leaf whose descriptor asks for the library card
|
|
151
|
+
(`chrome: "card"`): the host draws the card, its title with the
|
|
152
|
+
count the leaf reports, the leaf's actions in the title row and the
|
|
153
|
+
library empty state. The leaf reports them through the `onHeader`
|
|
154
|
+
mount prop and renders only its rows. -->
|
|
155
|
+
<CnWidgetWrapper
|
|
156
|
+
v-else-if="wrapsMountLeaf"
|
|
157
|
+
:title="leafCardTitle"
|
|
158
|
+
titleIconPosition="left"
|
|
159
|
+
:showActions="false"
|
|
160
|
+
:showRefresh="false"
|
|
161
|
+
:showRequestFeature="false"
|
|
162
|
+
:headerLink="cardHeaderLink"
|
|
163
|
+
class="cn-detail-page__leaf-card"
|
|
164
|
+
data-testid="cn-detail-widget-leaf-card">
|
|
165
|
+
<template v-if="leafCardIcon" #title-icon>
|
|
166
|
+
<CnIcon :name="leafCardIcon" :size="20" />
|
|
167
|
+
</template>
|
|
168
|
+
<template v-if="rendererHeader.count !== null" #title-meta>
|
|
169
|
+
<span class="cn-detail-page__catalog-count" data-testid="cn-detail-widget-header-count">{{ rendererHeader.count }}</span>
|
|
170
|
+
</template>
|
|
171
|
+
<template v-if="hasRendererHeaderItems" #actions>
|
|
172
|
+
<CnDetailWidgetHeaderItems :header="rendererHeader" />
|
|
173
|
+
</template>
|
|
174
|
+
<CnWidgetEmptyState
|
|
175
|
+
v-if="rendererHeader.empty"
|
|
176
|
+
:name="rendererHeader.empty.name"
|
|
177
|
+
:description="rendererHeader.empty.description" />
|
|
178
|
+
<CnLeafMountHost
|
|
179
|
+
:provider="integrationProvider"
|
|
180
|
+
:mountProps="integrationMountProps" />
|
|
181
|
+
</CnWidgetWrapper>
|
|
182
|
+
|
|
150
183
|
<CnLeafMountHost
|
|
151
184
|
v-else-if="isMountIntegration"
|
|
152
185
|
:provider="integrationProvider"
|
|
@@ -293,6 +326,7 @@ import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
|
|
|
293
326
|
import CnObjectFilesWidget from '../CnObjectFilesWidget/CnObjectFilesWidget.vue'
|
|
294
327
|
import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
295
328
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
329
|
+
import CnWidgetEmptyState from '../CnWidgetEmptyState/CnWidgetEmptyState.vue'
|
|
296
330
|
import CnDetailWidgetHeaderItems from './CnDetailWidgetHeaderItems.vue'
|
|
297
331
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
298
332
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
@@ -373,6 +407,7 @@ export default {
|
|
|
373
407
|
CnObjectDataWidget,
|
|
374
408
|
CnObjectGeoWidget,
|
|
375
409
|
CnRelatedObjectsWidget,
|
|
410
|
+
CnWidgetEmptyState,
|
|
376
411
|
CnWidgetWrapper,
|
|
377
412
|
NcActionButton,
|
|
378
413
|
CnEmptyContent,
|
|
@@ -628,7 +663,7 @@ export default {
|
|
|
628
663
|
* `header` event (screens-relation-pages): a link and a note,
|
|
629
664
|
* with the list's total already filled in.
|
|
630
665
|
*/
|
|
631
|
-
rendererHeader: { link: null, note: '', actions: [], pill: null },
|
|
666
|
+
rendererHeader: { link: null, note: '', actions: [], pill: null, count: null, empty: null },
|
|
632
667
|
}
|
|
633
668
|
},
|
|
634
669
|
|
|
@@ -844,6 +879,34 @@ export default {
|
|
|
844
879
|
&& typeof provider.unmount === 'function'
|
|
845
880
|
},
|
|
846
881
|
|
|
882
|
+
/**
|
|
883
|
+
* Whether a mount-mode leaf gets the library card: on a card surface,
|
|
884
|
+
* grid cell included, when its descriptor asks for it (`chrome:
|
|
885
|
+
* "card"`). Opt-in, because a leaf that draws its own card would
|
|
886
|
+
* otherwise sit in two.
|
|
887
|
+
*
|
|
888
|
+
* @return {boolean}
|
|
889
|
+
* @spec openspec/changes/integration-leaf-card/specs/integration-leaf-card/spec.md#requirement-a-mount-mode-leaf-can-take-the-library-card
|
|
890
|
+
*/
|
|
891
|
+
wrapsMountLeaf() {
|
|
892
|
+
return !this.isBare && this.isMountIntegration && this.integrationProvider.chrome === 'card'
|
|
893
|
+
},
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* The leaf card's title: the manifest's, else the descriptor's label.
|
|
897
|
+
*
|
|
898
|
+
* @return {string} The title in the user's language.
|
|
899
|
+
*/
|
|
900
|
+
leafCardTitle() {
|
|
901
|
+
const title = widgetTitleOf(this.widget)
|
|
902
|
+
return title ? this.translateLabel(title) : (this.integrationProvider?.label || '')
|
|
903
|
+
},
|
|
904
|
+
|
|
905
|
+
/** @return {string} The leaf card's icon: the manifest's, else the descriptor's. */
|
|
906
|
+
leafCardIcon() {
|
|
907
|
+
return this.widget?.icon || this.integrationProvider?.icon || ''
|
|
908
|
+
},
|
|
909
|
+
|
|
847
910
|
/**
|
|
848
911
|
* Whether this render is using a provider's widget as its bare surface.
|
|
849
912
|
*
|
|
@@ -1017,6 +1080,8 @@ export default {
|
|
|
1017
1080
|
// one that reads `objectId`.
|
|
1018
1081
|
integrationContext: this.widgetIntegrationContext,
|
|
1019
1082
|
...this.resolvedWidgetProps,
|
|
1083
|
+
// A carded leaf reports its title row and empty state here.
|
|
1084
|
+
...(this.wrapsMountLeaf ? { onHeader: this.onRendererHeader } : {}),
|
|
1020
1085
|
}
|
|
1021
1086
|
},
|
|
1022
1087
|
|
|
@@ -1221,11 +1286,12 @@ export default {
|
|
|
1221
1286
|
|
|
1222
1287
|
methods: {
|
|
1223
1288
|
/**
|
|
1224
|
-
* Take the header a content-only renderer,
|
|
1225
|
-
* titled card, reports.
|
|
1289
|
+
* Take the header a content-only renderer, an app widget in the
|
|
1290
|
+
* titled card, or a carded mount-mode leaf (`onHeader`) reports.
|
|
1226
1291
|
*
|
|
1227
|
-
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null, actions?: Array<{label: string, icon?: string, run: () => unknown}
|
|
1292
|
+
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null, actions?: Array<{label: string, icon?: string, run: () => unknown}>, count?: number, empty?: {name: string, description?: string}|null}} header The header parts.
|
|
1228
1293
|
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
1294
|
+
* @spec openspec/changes/integration-leaf-card/specs/integration-leaf-card/spec.md#requirement-a-carded-leaf-reports-its-count-actions-and-empty-state
|
|
1229
1295
|
*/
|
|
1230
1296
|
onRendererHeader(header) {
|
|
1231
1297
|
const link = header && header.link && typeof header.link === 'object' ? header.link : null
|
|
@@ -1234,7 +1300,11 @@ export default {
|
|
|
1234
1300
|
// `action` (one) and `actions` (several) both count, `action` first.
|
|
1235
1301
|
const actions = [header && header.action, ...((header && Array.isArray(header.actions)) ? header.actions : [])].filter(usable)
|
|
1236
1302
|
const pill = header && header.pill && typeof header.pill.label === 'string' && header.pill.label !== '' ? header.pill : null
|
|
1237
|
-
|
|
1303
|
+
const count = header && Number.isFinite(header.count) ? header.count : null
|
|
1304
|
+
const empty = header && header.empty && typeof header.empty.name === 'string' && header.empty.name !== ''
|
|
1305
|
+
? { name: header.empty.name, description: typeof header.empty.description === 'string' ? header.empty.description : '' }
|
|
1306
|
+
: null
|
|
1307
|
+
this.rendererHeader = { link, note, actions, pill, count, empty }
|
|
1238
1308
|
},
|
|
1239
1309
|
|
|
1240
1310
|
/**
|
|
@@ -1372,6 +1442,31 @@ export default {
|
|
|
1372
1442
|
white-space: nowrap;
|
|
1373
1443
|
}
|
|
1374
1444
|
|
|
1445
|
+
/* A leaf card in a narrow cell keeps its title and count: the title row
|
|
1446
|
+
wraps and the actions move under it instead of squeezing the title out. */
|
|
1447
|
+
.cn-detail-page__leaf-card :deep(.cn-widget-wrapper__header) {
|
|
1448
|
+
flex-wrap: wrap;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.cn-detail-page__leaf-card :deep(.cn-widget-wrapper__header-left) {
|
|
1452
|
+
flex: 1 0 auto;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/* The count beside a card title: the grey 20px badge of the tab strip. */
|
|
1456
|
+
.cn-detail-page__catalog-count {
|
|
1457
|
+
background-color: var(--color-border);
|
|
1458
|
+
border-radius: 10px;
|
|
1459
|
+
box-sizing: border-box;
|
|
1460
|
+
color: var(--color-main-text);
|
|
1461
|
+
display: inline-block;
|
|
1462
|
+
font-size: 12px;
|
|
1463
|
+
font-weight: 600;
|
|
1464
|
+
line-height: 20px;
|
|
1465
|
+
min-width: 20px;
|
|
1466
|
+
padding: 0 6px;
|
|
1467
|
+
text-align: center;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1375
1470
|
.cn-detail-page__catalog-note {
|
|
1376
1471
|
color: var(--color-text-maxcontrast);
|
|
1377
1472
|
font-size: 14px;
|
|
@@ -368,7 +368,7 @@ export default {
|
|
|
368
368
|
| `showMassImport` | Boolean | `true` | Whether to show the mass Import action |
|
|
369
369
|
| `showMassCopy` | Boolean | `true` | Whether to show the mass Copy action |
|
|
370
370
|
| `allowExport` | Boolean | `false` | Opt-in flag for the native Export menu (CSV/Excel) next to the Add button. Renders only when `true` AND the resolved schema is flagged `exportable: true`; navigates to OpenRegister's export leaf (`GET /apps/openregister/api/objects/{register}/{schema}/export`), passing `$route.query` through as filters. Distinct from `showMassExport`, which exports the fetched/selected rows via a blob download. |
|
|
371
|
-
| `allowSavedViews` | Boolean |
|
|
371
|
+
| `allowSavedViews` | Boolean | unset | Saved views (saved-views-ui). Unset, the sidebar's Views tab (save, apply, edit, share, add to menu, copy a default, delete) is on for every page with a sidebar and a source a view can be scoped to: register and schema, `entitySource` or `savedViewsScope`. `false` turns it off. `true` also forces the sidebar and adds the toolbar control: a Views dropdown listing the user's OpenRegister saved-search views (`GET /apps/openregister/api/views`). Applying one writes its stored filters/search/sort into the route query (`_search` and `_order` reserved keys + plain filter keys); "Save current view…" persists the current route-query state via POST and toasts either way, naming the view (a failure also keeps the dialog open with the reason); own views can be deleted after confirmation. Emits `apply-view`. |
|
|
372
372
|
| `savedViewsScope` | String | `''` | Which pages share this page's saved views. By default a view is shared by every page over the same `register` and `schema` and shown on no other page, since a view is filters over one schema's fields. Set a name to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope` on save; views saved before scoping existed carry no scope and stay visible everywhere. |
|
|
373
373
|
| `massActionNameField` | String | `'title'` | Property name used to display item names in dialogs |
|
|
374
374
|
| `nameFormatter` | Function | `null` | Custom formatter for item names in dialogs; overrides `massActionNameField` |
|
|
@@ -947,7 +947,11 @@
|
|
|
947
947
|
<CnStatusBadge v-for="(pill, index) in item.pills"
|
|
948
948
|
:key="index"
|
|
949
949
|
:label="pill.label"
|
|
950
|
-
:variant="pill.variant"
|
|
950
|
+
:variant="pill.variant">
|
|
951
|
+
<template v-if="pill.icon" #icon>
|
|
952
|
+
<CnIcon :name="pill.icon" :size="14" />
|
|
953
|
+
</template>
|
|
954
|
+
</CnStatusBadge>
|
|
951
955
|
<span v-if="item.due" class="cn-index-page__map-due">{{ item.due }}</span>
|
|
952
956
|
</span>
|
|
953
957
|
</div>
|
|
@@ -995,6 +999,7 @@
|
|
|
995
999
|
:swimlaneField="board.swimlaneField || ''"
|
|
996
1000
|
:dueRule="board.dueRule || null"
|
|
997
1001
|
:cardRoles="board.card || null"
|
|
1002
|
+
:pillLooks="refLooks"
|
|
998
1003
|
:colorField="board.colorField || ''"
|
|
999
1004
|
:sumField="board.sumField || ''"
|
|
1000
1005
|
:sumFormat="boardSumFormat"
|
|
@@ -1047,7 +1052,7 @@
|
|
|
1047
1052
|
v-else-if="currentViewMode === 'list'"
|
|
1048
1053
|
:objects="displayObjects"
|
|
1049
1054
|
:schema="effectiveSchema"
|
|
1050
|
-
:config="
|
|
1055
|
+
:config="renderedListConfig"
|
|
1051
1056
|
:selectable="selectable"
|
|
1052
1057
|
:selectedIds="internalSelectedIds"
|
|
1053
1058
|
:rowKey="rowKey"
|
|
@@ -1107,6 +1112,7 @@
|
|
|
1107
1112
|
:clickToView="rowClickOpens"
|
|
1108
1113
|
:cardFields="resolvedCardFields"
|
|
1109
1114
|
:cardLayout="cardLayout"
|
|
1115
|
+
:pillLooks="refLooks"
|
|
1110
1116
|
:cardFieldColumns="cardFieldColumns"
|
|
1111
1117
|
:accentOf="cardAccent"
|
|
1112
1118
|
:selectedIds="internalSelectedIds"
|
|
@@ -1379,7 +1385,7 @@ import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.
|
|
|
1379
1385
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
1380
1386
|
import { compileRowClass } from '../../utils/rowClassRules.js'
|
|
1381
1387
|
import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
1382
|
-
import { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, schemaSlug, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
1388
|
+
import { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, pageOffersSavedViews, savedViewScope, schemaSlug, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
1383
1389
|
import { savedViewBadge, savedViewOverrides } from '../../utils/savedViewOverrides.js'
|
|
1384
1390
|
import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
|
|
1385
1391
|
import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js'
|
|
@@ -1500,11 +1506,22 @@ function declaredFilterFields(props) {
|
|
|
1500
1506
|
* @return {object} The `{ fieldKey: values }` map, empty when the link carries none.
|
|
1501
1507
|
*/
|
|
1502
1508
|
function namedFiltersFromRoute(instance, props) {
|
|
1509
|
+
const route = instance && instance.proxy && instance.proxy.$route
|
|
1510
|
+
return namedFiltersFromQuery((route && route.query) || {}, props)
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* The named-source filters a query carries, as `namedFiltersFromRoute` reads them.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {object} query The route query.
|
|
1517
|
+
* @param {object} props The CnIndexPage props.
|
|
1518
|
+
*
|
|
1519
|
+
* @return {object} The `{ fieldKey: values }` map.
|
|
1520
|
+
*/
|
|
1521
|
+
function namedFiltersFromQuery(query, props) {
|
|
1503
1522
|
if (!props.entitySource) {
|
|
1504
1523
|
return {}
|
|
1505
1524
|
}
|
|
1506
|
-
const route = instance && instance.proxy && instance.proxy.$route
|
|
1507
|
-
const query = (route && route.query) || {}
|
|
1508
1525
|
const properties = declaredFilterFields(props)
|
|
1509
1526
|
const out = {}
|
|
1510
1527
|
|
|
@@ -1752,6 +1769,8 @@ export default {
|
|
|
1752
1769
|
|
|
1753
1770
|
inject: {
|
|
1754
1771
|
cnCustomComponents: { default: () => ({}) },
|
|
1772
|
+
/** The app's named formatters, provided by CnAppRoot, for the map list's pills and template tokens. */
|
|
1773
|
+
cnFormatters: { default: () => ({}) },
|
|
1755
1774
|
/** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
|
|
1756
1775
|
cnPermissions: { default: () => [] },
|
|
1757
1776
|
/** The app config an ancestor provides, for `@config.<key>` in quick-filter labels. */
|
|
@@ -2947,9 +2966,11 @@ export default {
|
|
|
2947
2966
|
},
|
|
2948
2967
|
|
|
2949
2968
|
/**
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
2952
|
-
*
|
|
2969
|
+
* Saved views (saved-views-ui). Unset, the sidebar's Views tab is on
|
|
2970
|
+
* for every page with a sidebar and a source a view can be scoped to
|
|
2971
|
+
* (register and schema, `entitySource` or `savedViewsScope`); `false`
|
|
2972
|
+
* turns it off. `true` also forces the sidebar and adds the toolbar
|
|
2973
|
+
* control, where a Views dropdown lists the
|
|
2953
2974
|
* current user's OpenRegister saved-search views
|
|
2954
2975
|
* (`GET /apps/openregister/api/views`); applying one writes its
|
|
2955
2976
|
* stored filters/search/sort into the route query (reusing the
|
|
@@ -2961,7 +2982,7 @@ export default {
|
|
|
2961
2982
|
*/
|
|
2962
2983
|
allowSavedViews: {
|
|
2963
2984
|
type: Boolean,
|
|
2964
|
-
default:
|
|
2985
|
+
default: null,
|
|
2965
2986
|
},
|
|
2966
2987
|
|
|
2967
2988
|
/**
|
|
@@ -4108,6 +4129,8 @@ export default {
|
|
|
4108
4129
|
boardPrevPageSize: this.viewMode === 'board' ? ((this.pagination && this.pagination.limit) || 20) : null,
|
|
4109
4130
|
/** Resolved labels of reference columns: `{ [columnKey]: { [id]: label|null } }`. */
|
|
4110
4131
|
refLabels: {},
|
|
4132
|
+
/** Colour and icon names of the records badge columns point at: `{ [columnKey]: { [id]: { color, icon } } }`. */
|
|
4133
|
+
refLooks: {},
|
|
4111
4134
|
/** Count per tab-strip index, for the entries that asked for one; null = none. */
|
|
4112
4135
|
tabCounts: null,
|
|
4113
4136
|
// The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
|
|
@@ -5523,7 +5546,7 @@ export default {
|
|
|
5523
5546
|
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-6
|
|
5524
5547
|
*/
|
|
5525
5548
|
savedViewsForSidebar() {
|
|
5526
|
-
if (!this.
|
|
5549
|
+
if (!this.savedViewsOffered) {
|
|
5527
5550
|
return null
|
|
5528
5551
|
}
|
|
5529
5552
|
return {
|
|
@@ -6022,6 +6045,12 @@ export default {
|
|
|
6022
6045
|
*/
|
|
6023
6046
|
renderedColumns() {
|
|
6024
6047
|
let cols = this.tableColumns
|
|
6048
|
+
if (this.refLookSpecs.length > 0) {
|
|
6049
|
+
const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key))
|
|
6050
|
+
cols = cols.map((col) => (col && typeof col === 'object' && lookKeys.has(col.key)
|
|
6051
|
+
? { ...col, widgetProps: { ...(col.widgetProps || {}), looks: this.refLooks[col.key] || {} } }
|
|
6052
|
+
: col))
|
|
6053
|
+
}
|
|
6025
6054
|
if (this.refLabelSpecs.length > 0) {
|
|
6026
6055
|
const byKey = new Map(this.refLabelSpecs.map((s) => [s.key, s]))
|
|
6027
6056
|
cols = cols.map((col) => {
|
|
@@ -6092,6 +6121,108 @@ export default {
|
|
|
6092
6121
|
*
|
|
6093
6122
|
* @return {Object<string, string[]>} Distinct ids per column key.
|
|
6094
6123
|
*/
|
|
6124
|
+
/**
|
|
6125
|
+
* The pills that take their tone and icon from the record they point at
|
|
6126
|
+
* (`resolve: { colorField?, iconField?, register?, schema? }`): badge
|
|
6127
|
+
* columns and the list view's leading and trailing cells
|
|
6128
|
+
* (`widgetProps.resolve`), the board card's pill (`board.card.resolve`)
|
|
6129
|
+
* and the record card's pills (`cardLayout` headPill, footerPill, chips).
|
|
6130
|
+
* Register and schema default to the field's reference in the schema.
|
|
6131
|
+
*
|
|
6132
|
+
* @return {Array<{key: string, register: string, schema: string, colorField: string, iconField: string}>} The specs.
|
|
6133
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
6134
|
+
*/
|
|
6135
|
+
refLookSpecs() {
|
|
6136
|
+
const list = this.listConfig || {}
|
|
6137
|
+
const listCells = [list.leading, ...(Array.isArray(list.trailing) ? list.trailing : [list.trailing])]
|
|
6138
|
+
const badgeSources = [...this.tableColumns, ...listCells]
|
|
6139
|
+
.filter((c) => c && typeof c === 'object' && c.widget === 'badge' && c.widgetProps)
|
|
6140
|
+
.map((c) => ({ key: c.key, resolve: c.widgetProps.resolve }))
|
|
6141
|
+
// The board card's pill (`board.card.pill` with `board.card.resolve`)
|
|
6142
|
+
// and the record card's pills (`cardLayout` headPill, footerPill,
|
|
6143
|
+
// chips, each with its own `resolve`).
|
|
6144
|
+
const card = this.board && this.board.card && typeof this.board.card === 'object' ? this.board.card : {}
|
|
6145
|
+
const layout = this.cardLayout && typeof this.cardLayout === 'object' ? this.cardLayout : {}
|
|
6146
|
+
const layoutPills = [layout.headPill, layout.footerPill, ...(Array.isArray(layout.chips) ? layout.chips : [])]
|
|
6147
|
+
.filter((def) => def && typeof def === 'object')
|
|
6148
|
+
.map((def) => ({ key: def.field, resolve: def.resolve }))
|
|
6149
|
+
// The map list's pills (`mapConfig.list.pills`, each with its own `resolve`).
|
|
6150
|
+
const mapListPills = (this.mapListEnabled && Array.isArray(this.mapConfig.list.pills) ? this.mapConfig.list.pills : [])
|
|
6151
|
+
.filter((def) => def && typeof def === 'object')
|
|
6152
|
+
.map((def) => ({ key: def.path, resolve: def.resolve }))
|
|
6153
|
+
const seen = new Set()
|
|
6154
|
+
const cols = [...badgeSources, { key: card.pill, resolve: card.resolve }, ...layoutPills, ...mapListPills].filter((c) => {
|
|
6155
|
+
const wanted = typeof c.key === 'string' && c.key !== '' && c.resolve && typeof c.resolve === 'object'
|
|
6156
|
+
&& (c.resolve.colorField || c.resolve.iconField) && !seen.has(c.key)
|
|
6157
|
+
if (wanted) {
|
|
6158
|
+
seen.add(c.key)
|
|
6159
|
+
}
|
|
6160
|
+
return wanted
|
|
6161
|
+
})
|
|
6162
|
+
if (cols.length === 0) {
|
|
6163
|
+
return []
|
|
6164
|
+
}
|
|
6165
|
+
const fields = this.effectiveSchema ? fieldsFromSchema(this.effectiveSchema, { includeReadOnly: true }) : []
|
|
6166
|
+
const specs = []
|
|
6167
|
+
for (const col of cols) {
|
|
6168
|
+
const resolve = col.resolve
|
|
6169
|
+
const field = fields.find((f) => f.key === col.key)
|
|
6170
|
+
const ref = (field && field.reference) || {}
|
|
6171
|
+
const register = resolve.register || ref.register || (typeof this.register === 'string' ? this.register : '')
|
|
6172
|
+
const schema = resolve.schema || (ref.schema !== undefined && ref.schema !== null ? String(ref.schema) : '')
|
|
6173
|
+
if (!register || !schema) {
|
|
6174
|
+
continue
|
|
6175
|
+
}
|
|
6176
|
+
specs.push({ key: col.key, register, schema, colorField: resolve.colorField || '', iconField: resolve.iconField || '' })
|
|
6177
|
+
}
|
|
6178
|
+
return specs
|
|
6179
|
+
},
|
|
6180
|
+
|
|
6181
|
+
/**
|
|
6182
|
+
* `listConfig` with the referenced records' looks on its badge cells,
|
|
6183
|
+
* as `renderedColumns` does for the table.
|
|
6184
|
+
*
|
|
6185
|
+
* @return {object} The list view's config.
|
|
6186
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
6187
|
+
*/
|
|
6188
|
+
renderedListConfig() {
|
|
6189
|
+
const list = this.listConfig
|
|
6190
|
+
if (!list || this.refLookSpecs.length === 0) {
|
|
6191
|
+
return list
|
|
6192
|
+
}
|
|
6193
|
+
const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key))
|
|
6194
|
+
const withLooks = (cell) => (cell && typeof cell === 'object' && lookKeys.has(cell.key)
|
|
6195
|
+
? { ...cell, widgetProps: { ...(cell.widgetProps || {}), looks: this.refLooks[cell.key] || {} } }
|
|
6196
|
+
: cell)
|
|
6197
|
+
return {
|
|
6198
|
+
...list,
|
|
6199
|
+
...(list.leading ? { leading: withLooks(list.leading) } : {}),
|
|
6200
|
+
...(list.trailing ? { trailing: Array.isArray(list.trailing) ? list.trailing.map(withLooks) : withLooks(list.trailing) } : {}),
|
|
6201
|
+
}
|
|
6202
|
+
},
|
|
6203
|
+
|
|
6204
|
+
/**
|
|
6205
|
+
* The ids each `refLookSpecs` column shows on screen.
|
|
6206
|
+
*
|
|
6207
|
+
* @return {{[key: string]: Array<string>}} Sorted ids by column key.
|
|
6208
|
+
*/
|
|
6209
|
+
refLookIds() {
|
|
6210
|
+
const out = {}
|
|
6211
|
+
for (const spec of this.refLookSpecs) {
|
|
6212
|
+
const ids = new Set()
|
|
6213
|
+
for (const row of this.displayObjects) {
|
|
6214
|
+
const v = row ? row[spec.key] : undefined
|
|
6215
|
+
;(Array.isArray(v) ? v : [v]).forEach((id) => {
|
|
6216
|
+
if (id !== null && id !== undefined && id !== '' && typeof id !== 'object') {
|
|
6217
|
+
ids.add(String(id))
|
|
6218
|
+
}
|
|
6219
|
+
})
|
|
6220
|
+
}
|
|
6221
|
+
out[spec.key] = [...ids].sort()
|
|
6222
|
+
}
|
|
6223
|
+
return out
|
|
6224
|
+
},
|
|
6225
|
+
|
|
6095
6226
|
refLabelIds() {
|
|
6096
6227
|
const out = {}
|
|
6097
6228
|
const facets = this.storeFacets || this.resolvedSidebar.facets || {}
|
|
@@ -6359,13 +6490,15 @@ export default {
|
|
|
6359
6490
|
const translated = {
|
|
6360
6491
|
...list,
|
|
6361
6492
|
title: this.cnTranslate(list.title),
|
|
6362
|
-
sub: list.sub ? this.cnTranslate(list.sub) : '',
|
|
6493
|
+
sub: Array.isArray(list.sub) ? list.sub.map((s) => this.cnTranslate(s)) : (list.sub ? this.cnTranslate(list.sub) : ''),
|
|
6363
6494
|
due: list.due ? this.cnTranslate(list.due) : '',
|
|
6364
6495
|
}
|
|
6365
6496
|
return mapListItems(this.displayObjects, translated, {
|
|
6366
6497
|
rowKey: this.rowKey,
|
|
6367
6498
|
located: (row) => Boolean(this.resolveRowGeometry(row)),
|
|
6368
6499
|
translate: (text) => this.cnTranslate(text),
|
|
6500
|
+
formatters: this.cnFormatters,
|
|
6501
|
+
looks: this.refLooks,
|
|
6369
6502
|
})
|
|
6370
6503
|
},
|
|
6371
6504
|
|
|
@@ -6664,7 +6797,16 @@ export default {
|
|
|
6664
6797
|
* @return {{ scope: string, register: string, schema: object|string|null }} The page's source.
|
|
6665
6798
|
*/
|
|
6666
6799
|
savedViewsPage() {
|
|
6667
|
-
return { scope: this.savedViewsScope, register: this.register, schema: this.schema }
|
|
6800
|
+
return { scope: this.savedViewsScope, register: this.register, schema: this.schema, source: this.entitySource }
|
|
6801
|
+
},
|
|
6802
|
+
|
|
6803
|
+
/**
|
|
6804
|
+
* Whether this page offers saved views in its sidebar.
|
|
6805
|
+
*
|
|
6806
|
+
* @return {boolean}
|
|
6807
|
+
*/
|
|
6808
|
+
savedViewsOffered() {
|
|
6809
|
+
return pageOffersSavedViews(this)
|
|
6668
6810
|
},
|
|
6669
6811
|
|
|
6670
6812
|
/**
|
|
@@ -7980,6 +8122,15 @@ export default {
|
|
|
7980
8122
|
},
|
|
7981
8123
|
},
|
|
7982
8124
|
|
|
8125
|
+
// Read the colour and icon of the records badge columns point at.
|
|
8126
|
+
refLookIds: {
|
|
8127
|
+
immediate: true,
|
|
8128
|
+
deep: true,
|
|
8129
|
+
handler() {
|
|
8130
|
+
this.loadRefLooks()
|
|
8131
|
+
},
|
|
8132
|
+
},
|
|
8133
|
+
|
|
7983
8134
|
// The Active line names a referenced value, not its id.
|
|
7984
8135
|
activeFilterRefIds: {
|
|
7985
8136
|
deep: true,
|
|
@@ -8237,7 +8388,7 @@ export default {
|
|
|
8237
8388
|
created() {
|
|
8238
8389
|
this.applyViewModeFromRoute()
|
|
8239
8390
|
this.pushAiContext()
|
|
8240
|
-
if (this.
|
|
8391
|
+
if (this.savedViewsOffered) {
|
|
8241
8392
|
this.fetchSavedViews()
|
|
8242
8393
|
}
|
|
8243
8394
|
this.selfActions = createSelfModeActions({
|
|
@@ -8431,6 +8582,45 @@ export default {
|
|
|
8431
8582
|
}))
|
|
8432
8583
|
},
|
|
8433
8584
|
|
|
8585
|
+
/**
|
|
8586
|
+
* Read the colour and icon names `refLookSpecs` columns need, for the
|
|
8587
|
+
* rows on screen, with the same resolver as the labels. A failure
|
|
8588
|
+
* leaves the badge as declared.
|
|
8589
|
+
*
|
|
8590
|
+
* @return {Promise<void>}
|
|
8591
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
8592
|
+
*/
|
|
8593
|
+
async loadRefLooks() {
|
|
8594
|
+
if (this.refLookSpecs.length === 0) {
|
|
8595
|
+
return
|
|
8596
|
+
}
|
|
8597
|
+
if (!this._refLabelResolver) {
|
|
8598
|
+
this._refLabelResolver = createRefLabelResolver(() => {
|
|
8599
|
+
try {
|
|
8600
|
+
return (this.list && this.list.objectStore) || useObjectStore()
|
|
8601
|
+
} catch {
|
|
8602
|
+
return null
|
|
8603
|
+
}
|
|
8604
|
+
})
|
|
8605
|
+
}
|
|
8606
|
+
const read = (spec, ids, name) => (name
|
|
8607
|
+
? this._refLabelResolver.resolve(spec.register, spec.schema, ids, name, { strict: true }).catch(() => ({}))
|
|
8608
|
+
: Promise.resolve({}))
|
|
8609
|
+
await Promise.all(this.refLookSpecs.map(async (spec) => {
|
|
8610
|
+
const known = this.refLooks[spec.key] || {}
|
|
8611
|
+
const ids = (this.refLookIds[spec.key] || []).filter((id) => !Object.hasOwn(known, id))
|
|
8612
|
+
if (ids.length === 0) {
|
|
8613
|
+
return
|
|
8614
|
+
}
|
|
8615
|
+
const [colors, icons] = await Promise.all([read(spec, ids, spec.colorField), read(spec, ids, spec.iconField)])
|
|
8616
|
+
const found = {}
|
|
8617
|
+
ids.forEach((id) => {
|
|
8618
|
+
found[id] = { color: (colors && colors[id]) || null, icon: (icons && icons[id]) || null }
|
|
8619
|
+
})
|
|
8620
|
+
this.refLooks = { ...this.refLooks, [spec.key]: { ...(this.refLooks[spec.key] || {}), ...found } }
|
|
8621
|
+
}))
|
|
8622
|
+
},
|
|
8623
|
+
|
|
8434
8624
|
/**
|
|
8435
8625
|
* Whether a saved view shows its record count (`viewCounts`).
|
|
8436
8626
|
*
|
|
@@ -11345,8 +11535,8 @@ export default {
|
|
|
11345
11535
|
|
|
11346
11536
|
/**
|
|
11347
11537
|
* Fetch the current user's saved views (own + public) from
|
|
11348
|
-
* OpenRegister's views API. Called from created() when
|
|
11349
|
-
*
|
|
11538
|
+
* OpenRegister's views API. Called from created() when the page
|
|
11539
|
+
* offers saved views.
|
|
11350
11540
|
*/
|
|
11351
11541
|
async fetchSavedViews() {
|
|
11352
11542
|
this.savedViewsLoading = true
|
|
@@ -11416,6 +11606,9 @@ export default {
|
|
|
11416
11606
|
query.savedView = this.appliedSavedViewId
|
|
11417
11607
|
}
|
|
11418
11608
|
const nav = this.$router.replace({ query })
|
|
11609
|
+
if (this.isNamedSource) {
|
|
11610
|
+
this.namedActiveFilters = namedFiltersFromQuery(query, this)
|
|
11611
|
+
}
|
|
11419
11612
|
// Swallow the duplicate-navigation rejection (Vue Router 3)
|
|
11420
11613
|
// when the applied view matches the current query.
|
|
11421
11614
|
if (nav && typeof nav.catch === 'function') {
|
|
@@ -11589,7 +11782,7 @@ export default {
|
|
|
11589
11782
|
return
|
|
11590
11783
|
}
|
|
11591
11784
|
const badge = this.savedViewMenuBadge(view)
|
|
11592
|
-
menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(badge ? { badge } : {}) })
|
|
11785
|
+
menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(typeof view.icon === 'string' && view.icon !== '' ? { icon: view.icon } : {}), ...(badge ? { badge } : {}) })
|
|
11593
11786
|
},
|
|
11594
11787
|
|
|
11595
11788
|
/**
|
|
@@ -12124,7 +12317,9 @@ export default {
|
|
|
12124
12317
|
if (resultData && resultData.success && this._refLabelResolver) {
|
|
12125
12318
|
this._refLabelResolver.invalidate()
|
|
12126
12319
|
this.refLabels = {}
|
|
12320
|
+
this.refLooks = {}
|
|
12127
12321
|
this.loadRefLabels()
|
|
12322
|
+
this.loadRefLooks()
|
|
12128
12323
|
}
|
|
12129
12324
|
},
|
|
12130
12325
|
|
|
@@ -216,7 +216,7 @@ With admin-only filter control:
|
|
|
216
216
|
| `propertiesGroupLabel` | String | `''` | Override label for the schema properties group. Falls back to `schema.title` |
|
|
217
217
|
| `defaultTab` | String | `'search-tab'` | Tab id that is active when the sidebar opens. Built-in ids: `'search-tab'`, `'columns-tab'` |
|
|
218
218
|
| `activeTab` | String | `''` | Switch to a tab while mounted, e.g. `'filters-tab'` |
|
|
219
|
-
| `savedViews` | Object | `null` | The page's saved views, shown in a third tab,
|
|
219
|
+
| `savedViews` | Object | `null` | The page's saved views, shown in a third tab, Views (`filters-tab`, CnSavedViewsPanel); every intent comes back as one `saved-view` event `{ action, view }` |
|
|
220
220
|
| `userIsAdmin` | Boolean | `true` | When `false`, schema properties with `adminOnly: true` are hidden from filters |
|
|
221
221
|
| `filterFields` | Object | `null` | Filter declarations for a page with no schema of its own. Wins over `schema`, and feeds the Search tab only |
|
|
222
222
|
|
|
@@ -287,11 +287,11 @@
|
|
|
287
287
|
</div>
|
|
288
288
|
</NcAppSidebarTab>
|
|
289
289
|
|
|
290
|
-
<!--
|
|
290
|
+
<!-- Views tab (id filters-tab): the page's saved views (save, list, manage, add to menu). -->
|
|
291
291
|
<NcAppSidebarTab
|
|
292
292
|
v-if="savedViews"
|
|
293
293
|
id="filters-tab"
|
|
294
|
-
:name="t('nextcloud-vue', '
|
|
294
|
+
:name="t('nextcloud-vue', 'Views')"
|
|
295
295
|
:order="3">
|
|
296
296
|
<template #icon>
|
|
297
297
|
<BookmarkOutline :size="20" />
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { translate as t } from '@nextcloud/l10n'
|
|
33
33
|
import { generateUrl } from '@nextcloud/router'
|
|
34
34
|
import { fetchEndpointSource } from '../../composables/useEndpointSource.js'
|
|
35
|
+
import { resolveDue } from '../../utils/dueText.js'
|
|
35
36
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
36
37
|
import { openRowTarget } from '../../utils/linkNavigation.js'
|
|
37
38
|
import { readPath } from '../../utils/readPath.js'
|
|
@@ -70,6 +71,9 @@ function appUrl(url) {
|
|
|
70
71
|
* - `itemsPath`: the rows' path in the response, when not `results`.
|
|
71
72
|
* - `limit`: the most rows shown (default 7).
|
|
72
73
|
* - `title`, `subtitle`: templates filled from a row (default `{title}`, none).
|
|
74
|
+
* - `due`: `{ field, ...dueText config }`: the trailing line says how a date
|
|
75
|
+
* stands ("3 days overdue", "Due today", the date or the days left), in
|
|
76
|
+
* place of `subtitle` (see `resolveDue`).
|
|
73
77
|
* - `href`: a template for a row's page (`/apps/dossiq/cases/{id}`).
|
|
74
78
|
* - `viewAll`: `{ href, label? }`.
|
|
75
79
|
* - `emptyText`: text for an empty list.
|
|
@@ -125,7 +129,7 @@ export default {
|
|
|
125
129
|
return {
|
|
126
130
|
id: String(id),
|
|
127
131
|
mainText: this.fill(this.config.title || '{title}', item) || String(id),
|
|
128
|
-
subText: this.
|
|
132
|
+
subText: this.subtitleOf(item),
|
|
129
133
|
href: this.config.href ? appUrl(fillTemplate(this.config.href, { ...item, id })) : '',
|
|
130
134
|
}
|
|
131
135
|
})
|
|
@@ -155,6 +159,22 @@ export default {
|
|
|
155
159
|
return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
|
|
156
160
|
},
|
|
157
161
|
|
|
162
|
+
/**
|
|
163
|
+
* A row's trailing line: how its due date stands, or the subtitle template.
|
|
164
|
+
*
|
|
165
|
+
* @spec openspec/changes/nc-dashboard-due/specs/nc-dashboard-widgets/spec.md#requirement-a-dashboard-row-says-how-its-date-stands
|
|
166
|
+
* @param {object} item The row.
|
|
167
|
+
* @return {string}
|
|
168
|
+
*/
|
|
169
|
+
subtitleOf(item) {
|
|
170
|
+
const due = this.config.due
|
|
171
|
+
if (due && typeof due === 'object' && due.field) {
|
|
172
|
+
const resolved = resolveDue(readPath(item, due.field), due, (text) => this.cnTranslate(text))
|
|
173
|
+
return resolved ? resolved.text : ''
|
|
174
|
+
}
|
|
175
|
+
return this.fill(this.config.subtitle, item)
|
|
176
|
+
},
|
|
177
|
+
|
|
158
178
|
/**
|
|
159
179
|
* A template filled from a row, translated first.
|
|
160
180
|
*
|
|
@@ -138,6 +138,7 @@ export default {
|
|
|
138
138
|
|---|---|---|---|
|
|
139
139
|
| `maxMetadata` | `Number` | `4` | Maximum number of schema properties to show in the metadata section. Properties are sorted by their `order` value before slicing |
|
|
140
140
|
| `accent` | `Object` | `null` | Status accent `{ variant, icon?, label? }`: a colored start border and, with `icon`, a colored icon before the title. `variant` is `success`, `warning`, `error`, `info` or `primary`; `label` is the icon's tooltip and accessible name |
|
|
141
|
+
| `pillLooks` | `Object` | `{}` | The referenced records' colour and icon names, by field and id (`{ [field]: { [id]: { color, icon } } }`, read by CnIndexPage). A record layout pill with `resolve: { colorField?, iconField? }` takes its record's tone and icon from here. |
|
|
141
142
|
|
|
142
143
|
## Slots
|
|
143
144
|
|