@conduction/nextcloud-vue 2.83.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/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.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/CnBodySections/CnBodySections.vue.js +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
- 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 +50 -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/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
- 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/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.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/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
- 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 +106 -3
- 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/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
- package/dist/esm/components/CnReportsPage/CnReportsPage.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/CnStagesWidget/CnStagesWidget.vue.js +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
- 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/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
- 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/CnWalkthrough/CnWalkthrough.vue.js +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.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/dialogs/CnTransitionInputDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.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 +38 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +78 -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 +1030 -533
- 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 +41 -4
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/detailWidgetVisibility.js +149 -0
- package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
- package/dist/esm/utils/formatMetric.js +39 -3
- package/dist/esm/utils/formatMetric.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/resolveManifestTokens.js +1 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +9 -0
- package/dist/esm/utils/sentinelTokens.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 +17159 -13422
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1030 -533
- package/l10n/en.json +38 -1
- package/l10n/nl.json +78 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnBodySections/CnBodySections.vue +22 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
- package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
- package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
- package/src/components/CnDetailPage/CnDetailPage.md +3 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
- package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
- package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
- 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/CnWalkthrough/CnWalkthrough.vue +16 -0
- 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/form-field.css +14 -0
- package/src/css/form-widget.css +11 -0
- package/src/css/index-page.css +49 -0
- package/src/css/kpi-card.css +13 -0
- package/src/css/look-board-reports.css +58 -0
- package/src/css/look-board.css +108 -7
- package/src/dialogs/CnTransitionInputDialog.vue +87 -7
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +137 -6
- 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 +40 -3
- package/src/utils/detailWidgetVisibility.js +145 -0
- package/src/utils/formatMetric.js +38 -2
- package/src/utils/gridStack.js +39 -0
- package/src/utils/humaniseKey.js +38 -0
- package/src/utils/notificationRuleLabel.js +5 -23
- package/src/utils/resolveManifestTokens.js +1 -0
- package/src/utils/sentinelTokens.js +9 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +8 -2
|
@@ -1253,6 +1253,7 @@ import {
|
|
|
1253
1253
|
stageEntry,
|
|
1254
1254
|
stageOf,
|
|
1255
1255
|
} from '../../utils/detailActionModel.js'
|
|
1256
|
+
import { detailWidgetVisible, fetchVisibilityRows, visibilityContextRefs, widgetConditionData } from '../../utils/detailWidgetVisibility.js'
|
|
1256
1257
|
import { reportBindingProblems } from '../../utils/diagnostics.js'
|
|
1257
1258
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
1258
1259
|
import { cnGridCellStyle, hasGridRow } from '../../utils/grid.js'
|
|
@@ -1260,6 +1261,7 @@ import { isActivityWidget, resolveHeaderMeta } from '../../utils/headerMeta.js'
|
|
|
1260
1261
|
import { patchStoredSelf } from '../../utils/patchStoredSelf.js'
|
|
1261
1262
|
import { setReadState } from '../../utils/recordInteractions.js'
|
|
1262
1263
|
import { slotRenders } from '../../utils/slotContent.js'
|
|
1264
|
+
import { evaluateVisibleWhen, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
|
|
1263
1265
|
import {
|
|
1264
1266
|
isCardWidgetDef,
|
|
1265
1267
|
isContentOnlyWidgetDef,
|
|
@@ -1512,8 +1514,8 @@ export default {
|
|
|
1512
1514
|
|
|
1513
1515
|
/**
|
|
1514
1516
|
* Offer the board look's tab list name to the tabs widget below this page
|
|
1515
|
-
* (`config.tabsLabel`, default "<type> parts")
|
|
1516
|
-
* widget reads
|
|
1517
|
+
* (`config.tabsLabel`, default "<type> parts"), and the records
|
|
1518
|
+
* `visibilityContext` names. Getter objects, so the widget reads them live.
|
|
1517
1519
|
*
|
|
1518
1520
|
* @return {object} The provided values.
|
|
1519
1521
|
* @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-folder-tabs-take-the-board-strip
|
|
@@ -1521,6 +1523,7 @@ export default {
|
|
|
1521
1523
|
provide() {
|
|
1522
1524
|
const isBoard = computed(() => this.isBoard)
|
|
1523
1525
|
const tabsLabel = computed(() => this.resolvedTabsLabel)
|
|
1526
|
+
const visibilityRows = computed(() => (this.visibilityRowsPending ? null : this.visibilityRows))
|
|
1524
1527
|
return {
|
|
1525
1528
|
// The page's own look reaches the tabs, the cards and the activity
|
|
1526
1529
|
// below it, also when it came from the `look` prop and not the app.
|
|
@@ -1530,6 +1533,15 @@ export default {
|
|
|
1530
1533
|
return tabsLabel.value
|
|
1531
1534
|
},
|
|
1532
1535
|
},
|
|
1536
|
+
|
|
1537
|
+
// The records `visibilityContext` names, keyed `@context.<name>`, so a tab's
|
|
1538
|
+
// `visibleWhen` reads the case type as a grid widget's does. Null
|
|
1539
|
+
// while they are on their way.
|
|
1540
|
+
cnDetailVisibilityRows: {
|
|
1541
|
+
get value() {
|
|
1542
|
+
return visibilityRows.value
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1533
1545
|
}
|
|
1534
1546
|
},
|
|
1535
1547
|
|
|
@@ -2572,6 +2584,25 @@ export default {
|
|
|
2572
2584
|
default: '',
|
|
2573
2585
|
},
|
|
2574
2586
|
|
|
2587
|
+
/**
|
|
2588
|
+
* Records the page reads beside the loaded one, so a `visibleWhen`
|
|
2589
|
+
* can ask about them (manifest `config.visibilityContext`). Each entry
|
|
2590
|
+
* is `{ <name>: { field, register, schema } }`: `field` is the
|
|
2591
|
+
* property of the loaded record holding the other record's id, and a
|
|
2592
|
+
* condition reads that record as `@context.<name>.<path>`. A case page that
|
|
2593
|
+
* declares `{ caseType: { field: "caseType", register: "dossiq",
|
|
2594
|
+
* schema: "caseType" } }` can show a widget for some case types only
|
|
2595
|
+
* with `{ field: "@context.caseType.category", op: "in", value: [...] }`.
|
|
2596
|
+
* A record that cannot be read is absent, so its condition fails safe.
|
|
2597
|
+
* Empty (the default) reads nothing extra.
|
|
2598
|
+
*
|
|
2599
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
2600
|
+
*/
|
|
2601
|
+
visibilityContext: {
|
|
2602
|
+
type: Object,
|
|
2603
|
+
default: () => ({}),
|
|
2604
|
+
},
|
|
2605
|
+
|
|
2575
2606
|
/**
|
|
2576
2607
|
* Whether the current user administers this instance. Shows the
|
|
2577
2608
|
* `adminOnly` header actions. `null` (the default) reads it from
|
|
@@ -2633,6 +2664,19 @@ export default {
|
|
|
2633
2664
|
type: String,
|
|
2634
2665
|
default: '',
|
|
2635
2666
|
},
|
|
2667
|
+
|
|
2668
|
+
/**
|
|
2669
|
+
* The record field the header names the record by (manifest
|
|
2670
|
+
* `config.titleField`, a dotted path such as `number`), as an invoice
|
|
2671
|
+
* page names the invoice number. An empty value falls back to the
|
|
2672
|
+
* display name. Empty (the default) changes nothing.
|
|
2673
|
+
*
|
|
2674
|
+
* @spec openspec/changes/detail-title-field/specs/detail-page/spec.md#requirement-a-detail-page-can-name-the-record-by-a-field
|
|
2675
|
+
*/
|
|
2676
|
+
titleField: {
|
|
2677
|
+
type: String,
|
|
2678
|
+
default: '',
|
|
2679
|
+
},
|
|
2636
2680
|
},
|
|
2637
2681
|
|
|
2638
2682
|
emits: [
|
|
@@ -2799,6 +2843,19 @@ export default {
|
|
|
2799
2843
|
|
|
2800
2844
|
data() {
|
|
2801
2845
|
return {
|
|
2846
|
+
/**
|
|
2847
|
+
* The records `visibilityContext` names, keyed `@context.<name>`: the row,
|
|
2848
|
+
* `null` when it could not be read, absent while it is on its way.
|
|
2849
|
+
*/
|
|
2850
|
+
visibilityRows: {},
|
|
2851
|
+
/** The `visibilityRefsKey` the rows in `visibilityRows` were read for. */
|
|
2852
|
+
visibilityRowsFor: '',
|
|
2853
|
+
/** Sequence of the last `visibilityContext` read, so a late answer for another record is dropped. */
|
|
2854
|
+
visibilityRowsSeq: 0,
|
|
2855
|
+
/** The answers of endpoint and source `visibleWhen` conditions on grid widgets, by widget id. */
|
|
2856
|
+
widgetConditionAnswers: {},
|
|
2857
|
+
/** Sequence of the last remote condition round. */
|
|
2858
|
+
widgetConditionSeq: 0,
|
|
2802
2859
|
/** The counts of the header's count pills (`countFrom`), by pill key (`type`, `status`). */
|
|
2803
2860
|
pillCounts: {},
|
|
2804
2861
|
/** Sequence of the last pill count round, so a late answer does not overwrite a newer one. */
|
|
@@ -3058,11 +3115,17 @@ export default {
|
|
|
3058
3115
|
* because a keyboard user who cannot see the page needs to know what
|
|
3059
3116
|
* they are being offered before they take it.
|
|
3060
3117
|
*
|
|
3118
|
+
* One library string with a placeholder, not "Skip to" from the host's
|
|
3119
|
+
* catalogue glued to the label: no app carried that fragment, so every
|
|
3120
|
+
* Dutch reader got "Skip to In behandeling nemen" (Zuiddrecht live
|
|
3121
|
+
* round 3), and a fragment cannot be put in Dutch word order anyway.
|
|
3122
|
+
*
|
|
3061
3123
|
* @return {string} The link text.
|
|
3062
3124
|
* @spec openspec/changes/case-page-and-list-as-a-place/specs/index-page/spec.md
|
|
3125
|
+
* @spec openspec/changes/detail-page-strings-dutch/specs/detail-page-strings-dutch/spec.md#requirement-the-detail-page-speaks-the-readers-language
|
|
3063
3126
|
*/
|
|
3064
3127
|
skipLinkLabel() {
|
|
3065
|
-
return
|
|
3128
|
+
return t('nextcloud-vue', 'Skip to {action}', { action: this.effectivePrimaryLabel })
|
|
3066
3129
|
},
|
|
3067
3130
|
|
|
3068
3131
|
// ── The action model (detail-action-model-and-case-surfaces) ────
|
|
@@ -3904,6 +3967,68 @@ export default {
|
|
|
3904
3967
|
return store.objects?.[this.objectType]?.[this.objectId] ?? null
|
|
3905
3968
|
},
|
|
3906
3969
|
|
|
3970
|
+
/**
|
|
3971
|
+
* The records `visibilityContext` names for the loaded record.
|
|
3972
|
+
*
|
|
3973
|
+
* @return {Array<{key: string, register: string, schema: string, id: string}>}
|
|
3974
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
3975
|
+
*/
|
|
3976
|
+
visibilityRefs() {
|
|
3977
|
+
return visibilityContextRefs(this.visibilityContext, this.resolvedObject)
|
|
3978
|
+
},
|
|
3979
|
+
|
|
3980
|
+
/**
|
|
3981
|
+
* Whether a record `visibilityContext` names is still on its way.
|
|
3982
|
+
*
|
|
3983
|
+
* @return {boolean}
|
|
3984
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
3985
|
+
*/
|
|
3986
|
+
visibilityRowsPending() {
|
|
3987
|
+
return this.visibilityRefs.length > 0 && this.visibilityRowsFor !== this.visibilityRefsKey
|
|
3988
|
+
},
|
|
3989
|
+
|
|
3990
|
+
/**
|
|
3991
|
+
* The named references as one key, so another record (or another case
|
|
3992
|
+
* type on the same record) reads them again.
|
|
3993
|
+
*
|
|
3994
|
+
* @return {string}
|
|
3995
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
3996
|
+
*/
|
|
3997
|
+
visibilityRefsKey() {
|
|
3998
|
+
return JSON.stringify(this.visibilityRefs)
|
|
3999
|
+
},
|
|
4000
|
+
|
|
4001
|
+
/**
|
|
4002
|
+
* What a grid widget's `visibleWhen` reads: the record, with the
|
|
4003
|
+
* `visibilityContext` records under `@context.<name>`. Null until the record loads.
|
|
4004
|
+
*
|
|
4005
|
+
* @return {object|null}
|
|
4006
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-grid-widget-shows-only-while-its-visiblewhen-holds-for-the-record
|
|
4007
|
+
*/
|
|
4008
|
+
widgetVisibilityData() {
|
|
4009
|
+
return widgetConditionData(this.resolvedObject, this.visibilityRows)
|
|
4010
|
+
},
|
|
4011
|
+
|
|
4012
|
+
/**
|
|
4013
|
+
* The grid widgets whose `visibleWhen` needs a request (endpoint or
|
|
4014
|
+
* source), with what they ask, as one key: a change re-asks them.
|
|
4015
|
+
*
|
|
4016
|
+
* @return {string}
|
|
4017
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-grid-widget-shows-only-while-its-visiblewhen-holds-for-the-record
|
|
4018
|
+
*/
|
|
4019
|
+
remoteWidgetConditionsKey() {
|
|
4020
|
+
if (!this.widgetVisibilityData || this.visibilityRowsPending) {
|
|
4021
|
+
return ''
|
|
4022
|
+
}
|
|
4023
|
+
const defs = [...(this.bodyGridWidgets || []), ...((this.activeView && this.activeView.widgets) || [])]
|
|
4024
|
+
const remote = defs.filter((def) => def && def.visibleWhen && typeof def.visibleWhen === 'object'
|
|
4025
|
+
&& !isLocallyDecidableVisibleWhen(def.visibleWhen))
|
|
4026
|
+
if (remote.length === 0) {
|
|
4027
|
+
return ''
|
|
4028
|
+
}
|
|
4029
|
+
return JSON.stringify([String(this.objectId || ''), remote.map((def) => [def.id, def.visibleWhen])])
|
|
4030
|
+
},
|
|
4031
|
+
|
|
3907
4032
|
/**
|
|
3908
4033
|
* The `@self` block of the resolved object (interaction markers live here).
|
|
3909
4034
|
*
|
|
@@ -3990,10 +4115,10 @@ export default {
|
|
|
3990
4115
|
},
|
|
3991
4116
|
|
|
3992
4117
|
/**
|
|
3993
|
-
* The resolved record's human display name
|
|
3994
|
-
*
|
|
3995
|
-
* `@self.name` → `name` / `title` /
|
|
3996
|
-
* lastName`. Empty string while the object is still loading or when no
|
|
4118
|
+
* The resolved record's human display name: the `titleField` value
|
|
4119
|
+
* when the page names one and it is filled, else the same fallback
|
|
4120
|
+
* chain the relation resolver uses: `@self.name` → `name` / `title` /
|
|
4121
|
+
* `displayName` → `firstName lastName`. Empty string while the object is still loading or when no
|
|
3997
4122
|
* name-like field exists (a raw id is never used as a name — ADR-062).
|
|
3998
4123
|
*
|
|
3999
4124
|
* @return {string}
|
|
@@ -4003,6 +4128,12 @@ export default {
|
|
|
4003
4128
|
if (!obj || typeof obj !== 'object') {
|
|
4004
4129
|
return ''
|
|
4005
4130
|
}
|
|
4131
|
+
if (this.titleField) {
|
|
4132
|
+
const named = stageOf(obj, this.titleField)
|
|
4133
|
+
if (named !== '') {
|
|
4134
|
+
return named
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4006
4137
|
const self = obj['@self'] || {}
|
|
4007
4138
|
const id = this.objectId !== null && this.objectId !== undefined ? String(this.objectId) : ''
|
|
4008
4139
|
const candidates = [self.name, self.title, obj.name, obj.title, obj.displayName]
|
|
@@ -4436,6 +4567,22 @@ export default {
|
|
|
4436
4567
|
},
|
|
4437
4568
|
|
|
4438
4569
|
watch: {
|
|
4570
|
+
// The records a condition names are read again for another record.
|
|
4571
|
+
visibilityRefsKey: {
|
|
4572
|
+
immediate: true,
|
|
4573
|
+
handler() {
|
|
4574
|
+
this.refreshVisibilityRows()
|
|
4575
|
+
},
|
|
4576
|
+
},
|
|
4577
|
+
|
|
4578
|
+
// Endpoint and source conditions on grid widgets are asked once per record.
|
|
4579
|
+
remoteWidgetConditionsKey: {
|
|
4580
|
+
immediate: true,
|
|
4581
|
+
handler() {
|
|
4582
|
+
this.refreshWidgetConditions()
|
|
4583
|
+
},
|
|
4584
|
+
},
|
|
4585
|
+
|
|
4439
4586
|
// The header's count pills count again for another record or other lists.
|
|
4440
4587
|
pillCountKey: {
|
|
4441
4588
|
immediate: true,
|
|
@@ -5674,6 +5821,62 @@ export default {
|
|
|
5674
5821
|
|| undefined
|
|
5675
5822
|
},
|
|
5676
5823
|
|
|
5824
|
+
/**
|
|
5825
|
+
* Read the records `visibilityContext` names for the loaded record. A
|
|
5826
|
+
* late answer for a record the page has left is dropped.
|
|
5827
|
+
*
|
|
5828
|
+
* @return {Promise<void>}
|
|
5829
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
5830
|
+
*/
|
|
5831
|
+
async refreshVisibilityRows() {
|
|
5832
|
+
const key = this.visibilityRefsKey
|
|
5833
|
+
const refs = this.visibilityRefs
|
|
5834
|
+
const seq = ++this.visibilityRowsSeq
|
|
5835
|
+
if (refs.length === 0) {
|
|
5836
|
+
// Write only on a change, so a page without `visibilityContext`
|
|
5837
|
+
// never touches the state its layout could read.
|
|
5838
|
+
if (Object.keys(this.visibilityRows).length > 0) {
|
|
5839
|
+
this.visibilityRows = {}
|
|
5840
|
+
}
|
|
5841
|
+
if (this.visibilityRowsFor !== key) {
|
|
5842
|
+
this.visibilityRowsFor = key
|
|
5843
|
+
}
|
|
5844
|
+
return
|
|
5845
|
+
}
|
|
5846
|
+
if (!this._visibilityRowCache) {
|
|
5847
|
+
this._visibilityRowCache = new Map()
|
|
5848
|
+
}
|
|
5849
|
+
const rows = await fetchVisibilityRows(refs, this._visibilityRowCache)
|
|
5850
|
+
if (seq !== this.visibilityRowsSeq) {
|
|
5851
|
+
return
|
|
5852
|
+
}
|
|
5853
|
+
this.visibilityRows = rows
|
|
5854
|
+
this.visibilityRowsFor = key
|
|
5855
|
+
},
|
|
5856
|
+
|
|
5857
|
+
/**
|
|
5858
|
+
* Ask the endpoint and source `visibleWhen` conditions of the grid
|
|
5859
|
+
* widgets, once per record. Each is fail-safe: an error hides the widget.
|
|
5860
|
+
*
|
|
5861
|
+
* @return {Promise<void>}
|
|
5862
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-grid-widget-shows-only-while-its-visiblewhen-holds-for-the-record
|
|
5863
|
+
*/
|
|
5864
|
+
async refreshWidgetConditions() {
|
|
5865
|
+
const seq = ++this.widgetConditionSeq
|
|
5866
|
+
if (this.remoteWidgetConditionsKey === '') {
|
|
5867
|
+
return
|
|
5868
|
+
}
|
|
5869
|
+
const [, entries] = JSON.parse(this.remoteWidgetConditionsKey)
|
|
5870
|
+
const ctx = { objectId: this.objectId, object: this.widgetVisibilityData || undefined }
|
|
5871
|
+
const answers = {}
|
|
5872
|
+
await Promise.all(entries.map(async ([id, cond]) => {
|
|
5873
|
+
answers[id] = await evaluateVisibleWhen(cond, ctx)
|
|
5874
|
+
}))
|
|
5875
|
+
if (seq === this.widgetConditionSeq) {
|
|
5876
|
+
this.widgetConditionAnswers = answers
|
|
5877
|
+
}
|
|
5878
|
+
},
|
|
5879
|
+
|
|
5677
5880
|
/**
|
|
5678
5881
|
* Drop the items that draw nothing, then close the rows they freed —
|
|
5679
5882
|
* `float: true` leaves the item below pinned under a gap nobody owns.
|
|
@@ -5696,7 +5899,10 @@ export default {
|
|
|
5696
5899
|
* Whether a layout item has anything to draw, so a grid cell is worth
|
|
5697
5900
|
* spending on it.
|
|
5698
5901
|
*
|
|
5699
|
-
*
|
|
5902
|
+
* A widget whose definition carries `visibleWhen` draws only while the
|
|
5903
|
+
* condition holds for the record (see `detailWidgetVisible`).
|
|
5904
|
+
*
|
|
5905
|
+
* Beyond that, TWO widget types are judged, and no others.
|
|
5700
5906
|
*
|
|
5701
5907
|
* `integration`, in the case where nothing can ever appear: a leaf from
|
|
5702
5908
|
* another app that is not registered, with no `requiredApp` to fall back
|
|
@@ -5743,6 +5949,18 @@ export default {
|
|
|
5743
5949
|
if (!widget) {
|
|
5744
5950
|
return true
|
|
5745
5951
|
}
|
|
5952
|
+
// The widget's own `visibleWhen`, read against the record and the
|
|
5953
|
+
// records `visibilityContext` names (a case's case type). Without
|
|
5954
|
+
// one the widget shows, as it always did, and the layout takes no
|
|
5955
|
+
// dependency on the record: a page without conditions re-lays out
|
|
5956
|
+
// exactly as often as before.
|
|
5957
|
+
if (widget.visibleWhen !== undefined && widget.visibleWhen !== null
|
|
5958
|
+
&& !detailWidgetVisible(widget, this.widgetVisibilityData, {
|
|
5959
|
+
pending: this.visibilityRowsPending,
|
|
5960
|
+
answers: this.widgetConditionAnswers,
|
|
5961
|
+
})) {
|
|
5962
|
+
return false
|
|
5963
|
+
}
|
|
5746
5964
|
// The board look shows the activity as the History tab. When a tabs
|
|
5747
5965
|
// widget already holds it, it is not also a body section.
|
|
5748
5966
|
if (this.isBoard && isActivityWidget(widget) && this.tabbedWidgetIds.has(widget.id)) {
|
|
@@ -725,7 +725,9 @@ export default {
|
|
|
725
725
|
if (this.widget && this.widget.type === 'files' && !(this.cnRegistry && this.cnRegistry.files) && this.register && this.schema) {
|
|
726
726
|
return CnObjectFilesWidget
|
|
727
727
|
}
|
|
728
|
-
|
|
728
|
+
// Inside a dashboard's tabs the dashboard's resolution holds.
|
|
729
|
+
const onDashboard = this.surface === 'app-dashboard' || this.surface === 'user-dashboard'
|
|
730
|
+
return resolveRegistryRenderer(this.widget, this.cnRegistry, { typeAsWritten: onDashboard })
|
|
729
731
|
},
|
|
730
732
|
|
|
731
733
|
/**
|
|
@@ -145,6 +145,7 @@ import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
|
145
145
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
146
146
|
import { formatBoardDate } from '../../utils/boardDate.js'
|
|
147
147
|
import { parseDateValue } from '../../utils/dateVariant.js'
|
|
148
|
+
import { formatMoney, unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
148
149
|
import { readPath } from '../../utils/readPath.js'
|
|
149
150
|
import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveFilterTokens.js'
|
|
150
151
|
|
|
@@ -246,6 +247,8 @@ export default {
|
|
|
246
247
|
cnTranslate: { default: () => (key) => key },
|
|
247
248
|
cnObjectContext: { default: null },
|
|
248
249
|
cnWorkspaceContext: { default: null },
|
|
250
|
+
/** The page's app config; its `currency` is the reporting currency a money value falls back to. */
|
|
251
|
+
cnAppConfig: { default: null },
|
|
249
252
|
/** Pre-bound action dispatcher of CnPageRenderer, for `action` / `headerAction`. */
|
|
250
253
|
cnDispatchAction: { default: null },
|
|
251
254
|
},
|
|
@@ -260,7 +263,7 @@ export default {
|
|
|
260
263
|
* A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
|
|
261
264
|
* `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. A resolved value works as a pill too: `map` is applied to the resolved label, and `variantMap` / `colorMap` look the tone up by the value of `resolve.toneField` on the referenced object when that is set (a status named by its `name`, coloured by its `state`), then by the resolved label, then by the stored value.
|
|
262
265
|
* - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
|
|
263
|
-
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year)
|
|
266
|
+
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
|
|
264
267
|
* `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.
|
|
265
268
|
* - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object.
|
|
266
269
|
* - `link` — `{ label, route? | href? }` at the foot.
|
|
@@ -900,7 +903,7 @@ export default {
|
|
|
900
903
|
*/
|
|
901
904
|
valueOf(record, field) {
|
|
902
905
|
const raw = readPath(record, field.path)
|
|
903
|
-
return this.format(raw, field)
|
|
906
|
+
return this.format(raw, field, record)
|
|
904
907
|
},
|
|
905
908
|
|
|
906
909
|
/**
|
|
@@ -919,7 +922,7 @@ export default {
|
|
|
919
922
|
if (!spec.path && typeof spec.text === 'string') {
|
|
920
923
|
return this.tr(spec.text)
|
|
921
924
|
}
|
|
922
|
-
const value = this.format(readPath(record, spec.path), spec)
|
|
925
|
+
const value = this.format(readPath(record, spec.path), spec, record)
|
|
923
926
|
if (value === '') {
|
|
924
927
|
return ''
|
|
925
928
|
}
|
|
@@ -933,10 +936,12 @@ export default {
|
|
|
933
936
|
* Format a raw value.
|
|
934
937
|
*
|
|
935
938
|
* @param {unknown} raw The value.
|
|
936
|
-
* @param {object} spec `{ format?, map?, itemPath? }`.
|
|
939
|
+
* @param {object} spec `{ format?, map?, itemPath?, currency?, currencyField?, decimals? }`.
|
|
940
|
+
* @param {object|null} [record] The object shown, which `currencyField` reads.
|
|
937
941
|
* @return {string} The text, '' when empty.
|
|
942
|
+
* @spec openspec/changes/facts-currency-format/specs/facts-widget/spec.md#requirement-a-facts-entry-can-show-a-money-amount
|
|
938
943
|
*/
|
|
939
|
-
format(raw, spec) {
|
|
944
|
+
format(raw, spec, record = null) {
|
|
940
945
|
if (raw === null || raw === undefined || raw === '') {
|
|
941
946
|
return ''
|
|
942
947
|
}
|
|
@@ -961,6 +966,14 @@ export default {
|
|
|
961
966
|
? formatBoardDate(date, new Date(date.getFullYear() + 1, 0, 1))
|
|
962
967
|
: formatBoardDate(date)
|
|
963
968
|
}
|
|
969
|
+
if (format === 'currency') {
|
|
970
|
+
const money = formatMoney(raw, { currency: spec.currency, currencyField: spec.currencyField, decimals: spec.decimals }, record, unwrapAppConfig(this.cnAppConfig))
|
|
971
|
+
if (money !== '') {
|
|
972
|
+
return money
|
|
973
|
+
}
|
|
974
|
+
// Not a number: shown as written, never as "[object Object]".
|
|
975
|
+
return typeof raw === 'object' ? '' : String(raw)
|
|
976
|
+
}
|
|
964
977
|
if (typeof raw === 'object') {
|
|
965
978
|
const label = raw.title || raw.name || raw.label || raw.displayName
|
|
966
979
|
return typeof label === 'string' ? label : ''
|
|
@@ -111,6 +111,14 @@
|
|
|
111
111
|
role="note"
|
|
112
112
|
:data-testid="`cn-field-list-band-${row.key}`">
|
|
113
113
|
<span class="cn-field-list-widget__band-text">{{ row.detail }}</span>
|
|
114
|
+
<NcButton
|
|
115
|
+
v-for="copy in row.copies"
|
|
116
|
+
:key="`copy-${copy.path}`"
|
|
117
|
+
variant="tertiary"
|
|
118
|
+
:data-testid="`cn-field-list-copy-${copy.path}-${row.key}`"
|
|
119
|
+
@click="copyValue(copy)">
|
|
120
|
+
{{ copy.label }}
|
|
121
|
+
</NcButton>
|
|
114
122
|
<NcButton
|
|
115
123
|
v-for="action in row.bandActions"
|
|
116
124
|
:key="action.id"
|
|
@@ -162,6 +170,16 @@
|
|
|
162
170
|
<p v-if="row.detail" class="cn-field-list-widget__detail">
|
|
163
171
|
{{ row.detail }}
|
|
164
172
|
</p>
|
|
173
|
+
<div v-if="row.detail && row.copies.length > 0" class="cn-field-list-widget__copies">
|
|
174
|
+
<NcButton
|
|
175
|
+
v-for="copy in row.copies"
|
|
176
|
+
:key="`copy-${copy.path}`"
|
|
177
|
+
variant="tertiary"
|
|
178
|
+
:data-testid="`cn-field-list-copy-${copy.path}-${row.key}`"
|
|
179
|
+
@click="copyValue(copy)">
|
|
180
|
+
{{ copy.label }}
|
|
181
|
+
</NcButton>
|
|
182
|
+
</div>
|
|
165
183
|
</li>
|
|
166
184
|
</ul>
|
|
167
185
|
|
|
@@ -211,6 +229,7 @@
|
|
|
211
229
|
</NcButton>
|
|
212
230
|
</form>
|
|
213
231
|
|
|
232
|
+
<span class="cn-field-list-widget__sr" aria-live="polite" data-testid="cn-field-list-copied">{{ copied }}</span>
|
|
214
233
|
<NcNoteCard v-if="notice" :type="noticeType" data-testid="cn-field-list-notice">
|
|
215
234
|
{{ notice }}
|
|
216
235
|
</NcNoteCard>
|
|
@@ -260,7 +279,7 @@ export default {
|
|
|
260
279
|
*
|
|
261
280
|
* - `field` (required) — the object field holding the array.
|
|
262
281
|
* - `key` — the item field that identifies an entry (default: the item itself for strings).
|
|
263
|
-
* - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix?, fallback? }, detail?: { template, when
|
|
282
|
+
* - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix?, fallback? }, detail?: { template, when?, copy?: [{ path, label }] } }`. `detail.copy` puts a copy button beside the detail line for each named value of the row (an empty value gets none), such as a DNS record's name and value.
|
|
264
283
|
* - `add` — `{ label?, fields: [{ key, label, type?: 'text'|'select', options?, required?, pattern?, patternMessage? }], unique?, defaults? }`.
|
|
265
284
|
* - `remove` — `{ label? }`; omitted = no remove.
|
|
266
285
|
* - `rowActions` — `[{ id, label, when?, placement?: 'band', request: { method?, url, payload? } }]`; tokens `@object.<field>`, `@objectId`, `@row.<field>`.
|
|
@@ -316,6 +335,8 @@ export default {
|
|
|
316
335
|
busy: '',
|
|
317
336
|
notice: '',
|
|
318
337
|
noticeType: 'error',
|
|
338
|
+
// What the last copy button put on the clipboard, said to a screen reader.
|
|
339
|
+
copied: '',
|
|
319
340
|
}
|
|
320
341
|
},
|
|
321
342
|
|
|
@@ -358,6 +379,7 @@ export default {
|
|
|
358
379
|
pill: this.pillOf(entry, spec.pill),
|
|
359
380
|
aside: spec.aside ? this.asideOf(entry, spec.aside) : '',
|
|
360
381
|
detail: spec.detail && this.matches(entry, spec.detail.when) ? this.fill(spec.detail.template, entry) : '',
|
|
382
|
+
copies: spec.detail && this.matches(entry, spec.detail.when) ? this.copiesOf(entry, spec.detail.copy) : [],
|
|
361
383
|
actions: this.actionsFor(entry),
|
|
362
384
|
cells: this.isTable ? this.cellsOf(entry) : [],
|
|
363
385
|
}
|
|
@@ -818,6 +840,40 @@ export default {
|
|
|
818
840
|
* @return {Promise<void>}
|
|
819
841
|
* @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-row-can-run-a-configured-request
|
|
820
842
|
*/
|
|
843
|
+
/**
|
|
844
|
+
* The copy buttons of a row's detail line: one per named value that is not empty.
|
|
845
|
+
*
|
|
846
|
+
* @param {object} entry The entry.
|
|
847
|
+
* @param {Array<{path: string, label?: string}>|undefined} declared The `detail.copy` list.
|
|
848
|
+
* @return {Array<{path: string, label: string, value: string}>}
|
|
849
|
+
*/
|
|
850
|
+
copiesOf(entry, declared) {
|
|
851
|
+
return (Array.isArray(declared) ? declared : [])
|
|
852
|
+
.filter((copy) => copy && typeof copy.path === 'string' && copy.path !== '')
|
|
853
|
+
.map((copy) => ({
|
|
854
|
+
path: copy.path,
|
|
855
|
+
label: this.tr(copy.label || 'Copy'),
|
|
856
|
+
value: String(readPath(entry, copy.path) ?? ''),
|
|
857
|
+
}))
|
|
858
|
+
.filter((copy) => copy.value !== '')
|
|
859
|
+
},
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Put one value on the clipboard and say so; a browser that refuses says that instead.
|
|
863
|
+
*
|
|
864
|
+
* @param {{label: string, value: string}} copy The copy button.
|
|
865
|
+
* @return {Promise<void>}
|
|
866
|
+
*/
|
|
867
|
+
async copyValue(copy) {
|
|
868
|
+
this.copied = ''
|
|
869
|
+
try {
|
|
870
|
+
await navigator.clipboard.writeText(copy.value)
|
|
871
|
+
this.copied = `${this.tr('Copied')}: ${copy.label}`
|
|
872
|
+
} catch {
|
|
873
|
+
this.notify(this.tr('Copying did not work. Select the text and copy it yourself.'))
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
|
|
821
877
|
async runAction(action, row) {
|
|
822
878
|
this.busy = row.key
|
|
823
879
|
this.notice = ''
|
|
@@ -912,6 +968,12 @@ export default {
|
|
|
912
968
|
|
|
913
969
|
.cn-field-list-widget__meta,
|
|
914
970
|
.cn-field-list-widget__aside,
|
|
971
|
+
.cn-field-list-widget__copies {
|
|
972
|
+
display: flex;
|
|
973
|
+
flex-wrap: wrap;
|
|
974
|
+
gap: 4px;
|
|
975
|
+
}
|
|
976
|
+
|
|
915
977
|
.cn-field-list-widget__detail {
|
|
916
978
|
font-size: 13px;
|
|
917
979
|
}
|
|
@@ -280,7 +280,7 @@ export default {
|
|
|
280
280
|
* @return {string} The formatted string.
|
|
281
281
|
*/
|
|
282
282
|
formatNumber(value) {
|
|
283
|
-
return formatMetricValue(value, this.resolvedFormat, this.configCtx)
|
|
283
|
+
return formatMetricValue(value, this.resolvedFormat, this.configCtx, null, this.effectiveTranslate)
|
|
284
284
|
},
|
|
285
285
|
|
|
286
286
|
/**
|