@conduction/nextcloud-vue 2.67.0 → 2.69.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/CnContextMenu/CnContextMenu.vue2.js +37 -26
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js +3 -3
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +21 -35
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +17 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +40 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +58 -16
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +87 -18
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +6 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +58 -48
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/defaultActions.js +6 -7
- package/dist/esm/components/CnIndexPage/defaultActions.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +33 -34
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js +3 -3
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +8 -2
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +2 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +70 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +23 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +3 -2
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useContextMenu.js +4 -2
- package/dist/esm/composables/useContextMenu.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +278 -11
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/store/plugins/dashboardLayouts.js +35 -1
- package/dist/esm/store/plugins/dashboardLayouts.js.map +1 -1
- package/dist/esm/utils/formatMetric.js +137 -16
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/resolveRowActions.js +130 -0
- package/dist/esm/utils/resolveRowActions.js.map +1 -0
- package/dist/esm/utils/rowActionItem.js +104 -0
- package/dist/esm/utils/rowActionItem.js.map +1 -0
- package/dist/esm/utils/timelineEvents.js +44 -1
- package/dist/esm/utils/timelineEvents.js.map +1 -1
- package/dist/esm/utils/validateManifest.js +59 -3
- package/dist/esm/utils/validateManifest.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1251 -232
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +2 -2
- package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
- package/src/components/CnDashboardPage/CnDashboardPage.md +2 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +34 -35
- package/src/components/CnDetailPage/CnDetailPage.md +6 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +67 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +92 -18
- package/src/components/CnIndexPage/CnIndexPage.md +21 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +61 -51
- package/src/components/CnIndexPage/defaultActions.js +6 -7
- package/src/components/CnRowActions/CnRowActions.vue +36 -37
- package/src/components/CnSetupWizard/CnSetupWizard.vue +8 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +2 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +82 -3
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
- package/src/composables/useContextMenu.js +4 -2
- package/src/mixins/pageViews.js +277 -10
- package/src/schemas/app-manifest-v2.schema.json +73 -9
- package/src/store/plugins/dashboardLayouts.js +34 -0
- package/src/utils/formatMetric.js +143 -15
- package/src/utils/resolveRowActions.js +127 -0
- package/src/utils/rowActionItem.js +100 -0
- package/src/utils/timelineEvents.js +43 -0
- package/src/utils/validateManifest.js +60 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -462,6 +462,11 @@
|
|
|
462
462
|
:register="nestedCreate.register"
|
|
463
463
|
:item="null"
|
|
464
464
|
:initialData="nestedCreate.initialData"
|
|
465
|
+
:excludeFields="nestedCreate.form.excludeFields"
|
|
466
|
+
:includeFields="nestedCreate.form.includeFields"
|
|
467
|
+
:fieldOverrides="nestedCreate.form.fieldOverrides"
|
|
468
|
+
:size="nestedCreate.form.size"
|
|
469
|
+
:columns="nestedCreate.form.columns"
|
|
465
470
|
:recoverDraft="false"
|
|
466
471
|
data-testid="cn-form-dialog-nested-create"
|
|
467
472
|
@confirm="onNestedCreateConfirm"
|
|
@@ -2645,7 +2650,12 @@ export default {
|
|
|
2645
2650
|
* the new object. When the referenced schema cannot be loaded, the
|
|
2646
2651
|
* term alone is saved as before, so the field never dead-ends.
|
|
2647
2652
|
*
|
|
2653
|
+
* The form is the one the schema's index page shows for its own Add
|
|
2654
|
+
* button: that page's `createOverride` saves it and its form settings
|
|
2655
|
+
* shape it, with the typed term in the name.
|
|
2656
|
+
*
|
|
2648
2657
|
* @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
|
|
2658
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2649
2659
|
* @param {object} field The reference field descriptor.
|
|
2650
2660
|
* @param {string} term The typed search term.
|
|
2651
2661
|
* @return {Promise<object|null>} The created object, or null.
|
|
@@ -2662,21 +2672,37 @@ export default {
|
|
|
2662
2672
|
store.registerObjectType(slug, schemaRef, register)
|
|
2663
2673
|
}
|
|
2664
2674
|
const labelField = this.referenceLabelField(field)
|
|
2675
|
+
const loadSchema = async () => {
|
|
2676
|
+
try {
|
|
2677
|
+
return await store.fetchSchema(slug)
|
|
2678
|
+
} catch {
|
|
2679
|
+
return null
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2665
2682
|
// The app may own how this schema is created (pipelinq's client and
|
|
2666
2683
|
// contact need a contact-first endpoint; a plain save 400s). A
|
|
2667
2684
|
// create override keeps this form and saves through the app's
|
|
2668
2685
|
// handler; a create modal (only when no override exists) opens the
|
|
2669
2686
|
// app's own dialog instead of the generic form.
|
|
2670
|
-
|
|
2687
|
+
//
|
|
2688
|
+
// A stored schema usually names its reference by id (`$ref: 28`)
|
|
2689
|
+
// while the manifest page names it by slug (`schema: 'client'`), so
|
|
2690
|
+
// when the reference itself matches no page, the referenced schema
|
|
2691
|
+
// is loaded and its slug, id and uuid are tried too.
|
|
2692
|
+
let schema
|
|
2693
|
+
let appCreate = this.appCreateFor(register, [schemaRef])
|
|
2694
|
+
if (!appCreate.page) {
|
|
2695
|
+
schema = await loadSchema()
|
|
2696
|
+
if (schema) {
|
|
2697
|
+
appCreate = this.appCreateFor(register, [schemaRef, schema.slug, schema.id, schema.uuid])
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2671
2700
|
if (!appCreate.override && appCreate.modal) {
|
|
2672
2701
|
return this.openAppCreateModal(appCreate.modal, store, slug, labelField, term)
|
|
2673
2702
|
}
|
|
2674
2703
|
const override = appCreate.override
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
schema = await store.fetchSchema(slug)
|
|
2678
|
-
} catch {
|
|
2679
|
-
schema = null
|
|
2704
|
+
if (schema === undefined) {
|
|
2705
|
+
schema = await loadSchema()
|
|
2680
2706
|
}
|
|
2681
2707
|
if (!schema || !schema.properties) {
|
|
2682
2708
|
const payload = { [labelField]: term }
|
|
@@ -2685,36 +2711,84 @@ export default {
|
|
|
2685
2711
|
: store.saveObject(slug, payload)
|
|
2686
2712
|
}
|
|
2687
2713
|
const initialData = {}
|
|
2688
|
-
|
|
2689
|
-
|
|
2714
|
+
const prefillKey = this.nestedPrefillKey(schema, labelField)
|
|
2715
|
+
if (term && prefillKey) {
|
|
2716
|
+
initialData[prefillKey] = term
|
|
2690
2717
|
}
|
|
2718
|
+
const form = appCreate.form || this.nestedFormConfig(null)
|
|
2691
2719
|
return new Promise((resolve) => {
|
|
2692
|
-
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, resolve }
|
|
2720
|
+
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, form, resolve }
|
|
2693
2721
|
})
|
|
2694
2722
|
},
|
|
2695
2723
|
|
|
2724
|
+
/**
|
|
2725
|
+
* The property of the referenced schema the typed term belongs in: the
|
|
2726
|
+
* reference's label field when the schema has it, else its name, title
|
|
2727
|
+
* or label. Visibility is decided by the form (a page `fieldOverrides`
|
|
2728
|
+
* entry can make a read-only name editable), not here.
|
|
2729
|
+
*
|
|
2730
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2731
|
+
* @param {object} schema The referenced schema.
|
|
2732
|
+
* @param {string} labelField The reference's label field.
|
|
2733
|
+
* @return {string|null} The property key, or null when none fits.
|
|
2734
|
+
*/
|
|
2735
|
+
nestedPrefillKey(schema, labelField) {
|
|
2736
|
+
const properties = (schema && schema.properties) || {}
|
|
2737
|
+
return [labelField, 'name', 'title', 'label'].find((key) => properties[key]) || null
|
|
2738
|
+
},
|
|
2739
|
+
|
|
2740
|
+
/**
|
|
2741
|
+
* The create-form settings of the index page that lists a schema, in
|
|
2742
|
+
* the shape the nested CnFormDialog binds: the same `excludeFields`,
|
|
2743
|
+
* `includeFields`, `fieldOverrides`, `formSize` and `formColumns`
|
|
2744
|
+
* CnIndexPage hands its own Add form, so creating from a picker shows
|
|
2745
|
+
* the form the page shows. Without a page, the form's defaults.
|
|
2746
|
+
*
|
|
2747
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2748
|
+
* @param {object|null} config The page's `config`, or null.
|
|
2749
|
+
* @return {{excludeFields: string[], includeFields: (string[]|null), fieldOverrides: object, size: string, columns: number}} The form settings.
|
|
2750
|
+
*/
|
|
2751
|
+
nestedFormConfig(config) {
|
|
2752
|
+
const c = config || {}
|
|
2753
|
+
const isMap = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
2754
|
+
return {
|
|
2755
|
+
excludeFields: Array.isArray(c.excludeFields) ? c.excludeFields : [],
|
|
2756
|
+
includeFields: Array.isArray(c.includeFields) ? c.includeFields : null,
|
|
2757
|
+
fieldOverrides: isMap(c.fieldOverrides) ? c.fieldOverrides : {},
|
|
2758
|
+
size: typeof c.formSize === 'string' && c.formSize !== '' ? c.formSize : 'normal',
|
|
2759
|
+
columns: c.formColumns === 2 ? 2 : 1,
|
|
2760
|
+
}
|
|
2761
|
+
},
|
|
2762
|
+
|
|
2696
2763
|
/**
|
|
2697
2764
|
* How the app creates objects of a schema, read off the manifest page
|
|
2698
2765
|
* that lists them: the index page whose `config.schema` (and, when it
|
|
2699
2766
|
* names one, `config.register`) matches. Its `config.createOverride` is
|
|
2700
2767
|
* resolved to the registered handler; its `config.createModal` is the
|
|
2701
|
-
* registry key of a `kind: 'modal'` dialog
|
|
2768
|
+
* registry key of a `kind: 'modal'` dialog; its form settings
|
|
2769
|
+
* (`fieldOverrides` and friends) shape the nested form.
|
|
2702
2770
|
*
|
|
2703
|
-
*
|
|
2771
|
+
* The schema is matched on any of the given references, so a caller
|
|
2772
|
+
* holding the referenced schema can pass its slug, id and uuid: the
|
|
2773
|
+
* manifest names a schema by slug, a stored `$ref` often by id.
|
|
2774
|
+
*
|
|
2775
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2704
2776
|
* @param {string} register The reference's register.
|
|
2705
|
-
* @param {string}
|
|
2706
|
-
* @return {{override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null)}} What the app declared.
|
|
2777
|
+
* @param {string|Array<string|number>} schemaRefs The schema's slug, id or uuid, one or several.
|
|
2778
|
+
* @return {{page: (object|null), override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null), form: (object|null)}} What the app declared.
|
|
2707
2779
|
*/
|
|
2708
|
-
appCreateFor(register,
|
|
2709
|
-
const none = { override: null, modal: null }
|
|
2780
|
+
appCreateFor(register, schemaRefs) {
|
|
2781
|
+
const none = { page: null, override: null, modal: null, form: null }
|
|
2710
2782
|
const pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []
|
|
2711
|
-
const wanted =
|
|
2783
|
+
const wanted = (Array.isArray(schemaRefs) ? schemaRefs : [schemaRefs])
|
|
2784
|
+
.filter((ref) => ref !== undefined && ref !== null && ref !== '')
|
|
2785
|
+
.map((ref) => String(ref).toLowerCase())
|
|
2712
2786
|
const page = pages.find((p) => {
|
|
2713
2787
|
const config = p && p.config
|
|
2714
2788
|
if (!config || config.schema === undefined || config.schema === null) {
|
|
2715
2789
|
return false
|
|
2716
2790
|
}
|
|
2717
|
-
if (String(config.schema).toLowerCase()
|
|
2791
|
+
if (!wanted.includes(String(config.schema).toLowerCase())) {
|
|
2718
2792
|
return false
|
|
2719
2793
|
}
|
|
2720
2794
|
return !config.register || String(config.register) === String(register)
|
|
@@ -2732,7 +2806,7 @@ export default {
|
|
|
2732
2806
|
const modal = modalEntry && modalEntry.kind === 'modal' && typeof this.cnOpenModal === 'function'
|
|
2733
2807
|
? config.createModal
|
|
2734
2808
|
: null
|
|
2735
|
-
return { override: override || null, modal }
|
|
2809
|
+
return { page, override: override || null, modal, form: this.nestedFormConfig(config) }
|
|
2736
2810
|
},
|
|
2737
2811
|
|
|
2738
2812
|
/**
|
|
@@ -866,6 +866,27 @@ actions, and the page's declaration stands as before. Wire the field on one
|
|
|
866
866
|
list first, then check `rowActionsNotDeclared` to see what the server is
|
|
867
867
|
offering that you have not declared yet.
|
|
868
868
|
|
|
869
|
+
The built-in View, Edit, Copy and Delete carry the ids `view`, `edit`, `copy` and `delete`, and match this block by id only, never by their label.
|
|
870
|
+
The right-click menu reads the same per-row list as the actions menu, so it drops the same refused actions.
|
|
871
|
+
|
|
872
|
+
## Placing built-in row actions
|
|
873
|
+
|
|
874
|
+
A `"builtin:view"`, `"builtin:edit"`, `"builtin:copy"` or `"builtin:delete"` string in `actions` puts that built-in at its position; enabled built-ins the array does not place are appended in the default order, and the `show*Action` toggles still decide whether each renders.
|
|
875
|
+
|
|
876
|
+
```json
|
|
877
|
+
"actionToggles": { "showViewAction": false },
|
|
878
|
+
"actions": [
|
|
879
|
+
"builtin:edit",
|
|
880
|
+
"builtin:copy",
|
|
881
|
+
{ "id": "file-list", "label": "File list", "handler": "openPublicationFiles" },
|
|
882
|
+
"builtin:delete"
|
|
883
|
+
]
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
This renders Edit, Copy, File list, Delete, and makes Edit the keyboard primary action.
|
|
887
|
+
The keyboard primary action is the first entry the menu shows and enables, so a hidden or disabled entry is skipped.
|
|
888
|
+
See the CnIndexPage docs page for the full rules.
|
|
889
|
+
|
|
869
890
|
## State indicators on a row
|
|
870
891
|
|
|
871
892
|
Declare `rowIndicators` and a row carries the flags a handler triages on. Each
|
|
@@ -692,11 +692,11 @@
|
|
|
692
692
|
</template>
|
|
693
693
|
</CnCardGrid>
|
|
694
694
|
|
|
695
|
-
<!-- Right-click context menu (positioned at cursor via CSS) -->
|
|
695
|
+
<!-- Right-click context menu (positioned at cursor via CSS). Same per-row list as the row actions menu, so the two never drift. -->
|
|
696
696
|
<CnContextMenu
|
|
697
697
|
v-model:open="contextMenuOpen"
|
|
698
|
-
:actions="
|
|
699
|
-
:targetItem="
|
|
698
|
+
:actions="rowActionsFor(contextMenuShownRow)"
|
|
699
|
+
:targetItem="contextMenuShownRow"
|
|
700
700
|
@action="onRowAction"
|
|
701
701
|
@close="closeContextMenu" />
|
|
702
702
|
|
|
@@ -812,8 +812,10 @@ import { resolveClaimedTeams, resolveClaimTokens, splitViewsIntoTabs, viewAsTab,
|
|
|
812
812
|
import { LIST_SHORTCUTS, listPaletteCommands, shortcutFor } from '../../utils/listShortcuts.js'
|
|
813
813
|
import { multiKeySort } from '../../utils/multiKeySort.js'
|
|
814
814
|
import { resolveDeepTokens, resolveFilterValue } from '../../utils/resolveFilterTokens.js'
|
|
815
|
+
import { resolveRowActions } from '../../utils/resolveRowActions.js'
|
|
815
816
|
import { resolveFilterMap } from '../../utils/routeFilters.js'
|
|
816
817
|
import { availableRowActions, DEFAULT_ROW_ACTION_FIELD, refusalReasonFor, undeclaredRowActions } from '../../utils/rowActionAvailability.js'
|
|
818
|
+
import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.js'
|
|
817
819
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
818
820
|
import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
819
821
|
import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
@@ -1687,7 +1689,11 @@ export default {
|
|
|
1687
1689
|
default: () => ({}),
|
|
1688
1690
|
},
|
|
1689
1691
|
|
|
1690
|
-
/**
|
|
1692
|
+
/**
|
|
1693
|
+
* Row action definitions, merged with the built-in View / Edit / Copy / Delete.
|
|
1694
|
+
* Objects are app actions.
|
|
1695
|
+
* A `"builtin:view"`, `"builtin:edit"`, `"builtin:copy"` or `"builtin:delete"` string places that built-in at its position when its `show*Action` toggle is on; enabled built-ins not placed are appended in that order.
|
|
1696
|
+
*/
|
|
1691
1697
|
actions: {
|
|
1692
1698
|
type: Array,
|
|
1693
1699
|
default: () => [],
|
|
@@ -2761,6 +2767,8 @@ export default {
|
|
|
2761
2767
|
folderRowLayouts: {},
|
|
2762
2768
|
/** The row the keyboard is on, or -1. */
|
|
2763
2769
|
focusedRowIndex: -1,
|
|
2770
|
+
// The row the context menu shows: set on open, never cleared on close, so a closing menu keeps its entries and a late close cannot null a reopened one.
|
|
2771
|
+
contextMenuShownRow: null,
|
|
2764
2772
|
/** The row a quick edit is open on, or null. */
|
|
2765
2773
|
quickEditRow: null,
|
|
2766
2774
|
/** The row as the server holds it, after a stale save. */
|
|
@@ -3473,7 +3481,7 @@ export default {
|
|
|
3473
3481
|
if (this.quickEditFields.length > 0) {
|
|
3474
3482
|
handlers['row-quick-edit'] = () => this.focusedRow && this.openQuickEdit(this.focusedRow)
|
|
3475
3483
|
}
|
|
3476
|
-
if (this.focusedRow && this.
|
|
3484
|
+
if (this.focusedRow && this.primaryRowActionFor(this.focusedRow)) {
|
|
3477
3485
|
handlers['row-primary'] = () => this.runPrimaryRowAction()
|
|
3478
3486
|
}
|
|
3479
3487
|
return handlers
|
|
@@ -4154,7 +4162,8 @@ export default {
|
|
|
4154
4162
|
},
|
|
4155
4163
|
|
|
4156
4164
|
/**
|
|
4157
|
-
* Merged actions:
|
|
4165
|
+
* Merged actions: the declared entries in order, each `"builtin:<id>"` placeholder replaced by that built-in when its toggle is on, then the enabled built-ins not placed.
|
|
4166
|
+
* See `resolveRowActions`.
|
|
4158
4167
|
*
|
|
4159
4168
|
* REQ-MAD-3 / REQ-MAD-4 / REQ-MAD-5 / REQ-MAD-6 / REQ-MAD-7
|
|
4160
4169
|
* (manifest-actions-dispatch) — for any action whose `handler`
|
|
@@ -4170,58 +4179,41 @@ export default {
|
|
|
4170
4179
|
registry: this.effectiveRegistry,
|
|
4171
4180
|
customComponents: this.effectiveCustomComponents,
|
|
4172
4181
|
}
|
|
4173
|
-
// Drop anything that is not an action OBJECT. A bare string — the
|
|
4174
|
-
// shorthand `"edit"` an author reasonably expects to mean "show the
|
|
4175
|
-
// built-in Edit" — has no `label` and no `icon`, so it rendered as a
|
|
4176
|
-
// full-height NcActionButton with nothing in it: an invisible,
|
|
4177
|
-
// clickable, inert row in the overflow menu. Observed on dossiq,
|
|
4178
|
-
// where `config.actions: ["create", "edit", "delete", {…}]` put three
|
|
4179
|
-
// blank rows above the real ones on four different index pages.
|
|
4180
|
-
//
|
|
4181
|
-
// Named, not silently swallowed: a dropped action is a capability the
|
|
4182
|
-
// author asked for and did not get, and the manifest schema now
|
|
4183
|
-
// rejects the shape at authoring time (`config.actions` is governed by
|
|
4184
|
-
// the same `action` definition as `pages[].actions`). Built-ins are
|
|
4185
|
-
// turned on with the `show*Action` toggles, not by naming them here.
|
|
4186
4182
|
// A named source may supply its own row actions, on the same
|
|
4187
4183
|
// precedence as its columns: what the manifest declares wins, and the
|
|
4188
|
-
// source is the DEFAULT for a page that declares none.
|
|
4189
|
-
//
|
|
4190
|
-
// field of the same adapter to be wired late, after `columns` and
|
|
4191
|
-
// `addLabel`.
|
|
4192
|
-
//
|
|
4193
|
-
// Note what this still cannot do: these are MERGED with the built-ins
|
|
4194
|
-
// below, so a source can add an action but cannot say "these and no
|
|
4195
|
-
// others". Suppressing a built-in is the `show*Action` toggles' job.
|
|
4184
|
+
// source is the DEFAULT for a page that declares none.
|
|
4185
|
+
// Either list may place built-ins with `"builtin:<id>"`; turning one on or off stays the `show*Action` toggles' job.
|
|
4196
4186
|
const manifestDeclared = this.actions && this.actions.length > 0
|
|
4197
4187
|
const declaredActions = manifestDeclared
|
|
4198
4188
|
? this.actions
|
|
4199
4189
|
: ((this.isNamedSource && this.namedSource && this.namedSource.rowActions) || [])
|
|
4200
4190
|
|
|
4201
|
-
const
|
|
4202
|
-
|
|
4203
|
-
if (a && typeof a === 'object' && !Array.isArray(a)) {
|
|
4191
|
+
const { actions, warnings } = resolveRowActions(declaredActions, this.defaultActions, {
|
|
4192
|
+
prepare: (a) => {
|
|
4204
4193
|
// A SOURCE action may say `action: 'open'`: open this row the
|
|
4205
4194
|
// way a row click does — the source's own navigation
|
|
4206
4195
|
// (`openRow`/`detailRoute`), with `edit-open` emitted so a
|
|
4207
4196
|
// host that configured a row target (`config.rowRoute`) wins.
|
|
4208
|
-
// Resolved here because a source is a composable with no
|
|
4209
|
-
//
|
|
4210
|
-
// it rendered as a dead menu entry, because `action` was a
|
|
4211
|
-
// key nothing read (dispatch reads `handler` and `type`).
|
|
4212
|
-
// Source-declared actions only: the manifest grammar for
|
|
4213
|
-
// navigation is `type`/`handler`, and stays so.
|
|
4197
|
+
// Resolved here because a source is a composable with no router.
|
|
4198
|
+
// Source-declared actions only: the manifest grammar for navigation is `type`/`handler`, and stays so.
|
|
4214
4199
|
if (!manifestDeclared && a.action === 'open' && typeof a.handler !== 'function') {
|
|
4215
|
-
|
|
4216
|
-
continue
|
|
4200
|
+
return { ...a, handler: (row) => this.openSourceRow(row) }
|
|
4217
4201
|
}
|
|
4218
|
-
|
|
4202
|
+
return dispatchAction(a, ctx)
|
|
4203
|
+
},
|
|
4204
|
+
})
|
|
4205
|
+
for (const warning of warnings) {
|
|
4206
|
+
// A toggle turned off at runtime (permissions, readOnly) legitimately silences a placeholder; the validator flags a statically-off one.
|
|
4207
|
+
if (warning.code === 'disabled-placeholder') {
|
|
4219
4208
|
continue
|
|
4220
4209
|
}
|
|
4221
|
-
//
|
|
4222
|
-
|
|
4210
|
+
// A dropped entry is always named; the placement hints are dev-only.
|
|
4211
|
+
if (warning.code === 'invalid-entry' || process.env.NODE_ENV !== 'production') {
|
|
4212
|
+
// eslint-disable-next-line no-console
|
|
4213
|
+
console.warn(`[CnIndexPage] "${this.title}": ${warning.message}`)
|
|
4214
|
+
}
|
|
4223
4215
|
}
|
|
4224
|
-
return
|
|
4216
|
+
return actions
|
|
4225
4217
|
},
|
|
4226
4218
|
|
|
4227
4219
|
hasRowActions() {
|
|
@@ -5467,9 +5459,20 @@ export default {
|
|
|
5467
5459
|
},
|
|
5468
5460
|
|
|
5469
5461
|
/**
|
|
5470
|
-
*
|
|
5471
|
-
*
|
|
5472
|
-
*
|
|
5462
|
+
* The first action a row's menu shows and enables: the page's order, narrowed by what the server allows, by `visible` / `visibleWhen`, and by `disabled`.
|
|
5463
|
+
* The keyboard therefore never runs what the menu hides or greys out.
|
|
5464
|
+
*
|
|
5465
|
+
* @param {object} row The row.
|
|
5466
|
+
* @return {(object|null)} The action, or null.
|
|
5467
|
+
* @spec openspec/changes/row-action-builtin-placement/specs/index-page/spec.md
|
|
5468
|
+
*/
|
|
5469
|
+
primaryRowActionFor(row) {
|
|
5470
|
+
return this.rowActionsFor(row).find((action) => isRowActionVisible(action, row)
|
|
5471
|
+
&& !(typeof action.disabled === 'function' ? action.disabled(row) : action.disabled)) || null
|
|
5472
|
+
},
|
|
5473
|
+
|
|
5474
|
+
/**
|
|
5475
|
+
* Run the focused row's primary action (see `primaryRowActionFor`).
|
|
5473
5476
|
*
|
|
5474
5477
|
* @return {void}
|
|
5475
5478
|
* @spec openspec/changes/working-list-row-actions/specs/index-page/spec.md
|
|
@@ -5479,14 +5482,14 @@ export default {
|
|
|
5479
5482
|
if (!row) {
|
|
5480
5483
|
return
|
|
5481
5484
|
}
|
|
5482
|
-
const
|
|
5485
|
+
const first = this.primaryRowActionFor(row)
|
|
5483
5486
|
if (!first) {
|
|
5484
5487
|
return
|
|
5485
5488
|
}
|
|
5486
5489
|
if (typeof first.handler === 'function') {
|
|
5487
5490
|
first.handler(row)
|
|
5488
5491
|
}
|
|
5489
|
-
this
|
|
5492
|
+
this.onRowAction(rowActionPayload(first, row))
|
|
5490
5493
|
},
|
|
5491
5494
|
|
|
5492
5495
|
/**
|
|
@@ -5803,16 +5806,22 @@ export default {
|
|
|
5803
5806
|
},
|
|
5804
5807
|
|
|
5805
5808
|
/**
|
|
5806
|
-
* Intercepts CnRowActions' bubbled `@action` so `handler: "none"`
|
|
5807
|
-
*
|
|
5809
|
+
* Intercepts CnRowActions' and CnContextMenu's bubbled `@action` so `handler: "none"` actions are dropped before re-emit.
|
|
5810
|
+
* A built-in is matched by its id, an app action by its label.
|
|
5808
5811
|
*
|
|
5809
|
-
* @param {{action: string, row: object}} payload The bubbled action payload.
|
|
5812
|
+
* @param {{action: string, row: object, id?: string, builtin?: boolean}} payload The bubbled action payload.
|
|
5810
5813
|
*/
|
|
5811
5814
|
onRowAction(payload) {
|
|
5812
|
-
const matched =
|
|
5815
|
+
const matched = payload.builtin === true
|
|
5816
|
+
? this.mergedActions.find((a) => a.builtin === true && a.id === payload.id)
|
|
5817
|
+
: this.mergedActions.find((a) => a.builtin !== true && a.label === payload.action)
|
|
5813
5818
|
if (matched && matched._dispatchSuppress) {
|
|
5814
5819
|
return
|
|
5815
5820
|
}
|
|
5821
|
+
/**
|
|
5822
|
+
* @event action A row action was chosen from a row's actions menu, its right-click menu or the keyboard primary action. Payload: `action` (the label), the row, the action's `id` when it has one, and `builtin: true` for a built-in View / Edit / Copy / Delete. Its handler has already run.
|
|
5823
|
+
* @type {{ action: string, row: object, id?: string, builtin?: boolean }}
|
|
5824
|
+
*/
|
|
5816
5825
|
this.$emit('action', payload)
|
|
5817
5826
|
},
|
|
5818
5827
|
|
|
@@ -6758,6 +6767,7 @@ export default {
|
|
|
6758
6767
|
// --- Context menu handlers ---
|
|
6759
6768
|
|
|
6760
6769
|
onRowContextMenu({ row, event }) {
|
|
6770
|
+
this.contextMenuShownRow = row
|
|
6761
6771
|
this.openContextMenu({ item: row, event })
|
|
6762
6772
|
},
|
|
6763
6773
|
|
|
@@ -14,9 +14,8 @@ import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
|
14
14
|
* without it, or when it returns null, View is a button.
|
|
15
15
|
* @param {{ onView: (row: object) => void, onEdit: (row: object) => void, onCopy: (row: object) => void, onDelete: (row: object) => void }} opts.handlers
|
|
16
16
|
* Click handlers bound to each emitted action, in the same order as `flags`.
|
|
17
|
-
* @return {Array<object>} The enabled actions in menu order (view, edit, copy,
|
|
18
|
-
*
|
|
19
|
-
* view additionally `to` when `viewTo` is given.
|
|
17
|
+
* @return {Array<object>} The enabled actions in menu order (view, edit, copy, delete), each `{id, builtin: true, label, icon, handler}`.
|
|
18
|
+
* The id is `view`, `edit`, `copy` or `delete`; delete additionally carries `destructive`, view additionally `to` when `viewTo` is given.
|
|
20
19
|
*/
|
|
21
20
|
export function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }) {
|
|
22
21
|
// t() at BUILD-actions time, not module time: the labels were string
|
|
@@ -26,20 +25,20 @@ export function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }
|
|
|
26
25
|
// consuming app's language is already resolved.
|
|
27
26
|
const out = []
|
|
28
27
|
if (flags.view) {
|
|
29
|
-
const view = { label: t('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView }
|
|
28
|
+
const view = { id: 'view', builtin: true, label: t('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView }
|
|
30
29
|
if (typeof viewTo === 'function') {
|
|
31
30
|
view.to = viewTo
|
|
32
31
|
}
|
|
33
32
|
out.push(view)
|
|
34
33
|
}
|
|
35
34
|
if (flags.edit) {
|
|
36
|
-
out.push({ label: t('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit })
|
|
35
|
+
out.push({ id: 'edit', builtin: true, label: t('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit })
|
|
37
36
|
}
|
|
38
37
|
if (flags.copy) {
|
|
39
|
-
out.push({ label: t('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy })
|
|
38
|
+
out.push({ id: 'copy', builtin: true, label: t('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy })
|
|
40
39
|
}
|
|
41
40
|
if (flags.del) {
|
|
42
|
-
out.push({ label: t('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete })
|
|
41
|
+
out.push({ id: 'delete', builtin: true, label: t('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete })
|
|
43
42
|
}
|
|
44
43
|
return out
|
|
45
44
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
:primary="primary"
|
|
4
4
|
:menuName="menuName"
|
|
5
5
|
data-testid="cn-row-actions">
|
|
6
|
-
<template v-for="{ action, link } in renderedActions" :key="action
|
|
6
|
+
<template v-for="{ action, link } in renderedActions" :key="actionKey(action)">
|
|
7
7
|
<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->
|
|
8
8
|
<NcActionLink
|
|
9
9
|
v-if="link"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
:target="link.target"
|
|
12
12
|
:title="getTitle(action)"
|
|
13
13
|
:class="{ 'cn-row-action--destructive': action.destructive }"
|
|
14
|
-
:data-testid="
|
|
14
|
+
:data-testid="actionTestId(action)"
|
|
15
15
|
closeAfterClick
|
|
16
16
|
@click="onLinkAction(action, link, $event)">
|
|
17
17
|
<template v-if="action.icon" #icon>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
:title="getTitle(action)"
|
|
26
26
|
:disabled="isDisabled(action)"
|
|
27
27
|
:class="{ 'cn-row-action--destructive': action.destructive }"
|
|
28
|
-
:data-testid="
|
|
28
|
+
:data-testid="actionTestId(action)"
|
|
29
29
|
closeAfterClick
|
|
30
30
|
@click="onAction(action)">
|
|
31
31
|
<template v-if="action.icon" #icon>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
import { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'
|
|
43
43
|
import { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'
|
|
44
44
|
import { isModifiedClick } from '../../utils/linkNavigation.js'
|
|
45
|
-
import {
|
|
45
|
+
import { isRowActionVisible, rowActionKey, rowActionPayload, rowActionTestId, slugifyActionLabel } from '../../utils/rowActionItem.js'
|
|
46
46
|
import { CnIcon } from '../CnIcon/index.js'
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -103,11 +103,13 @@ export default {
|
|
|
103
103
|
* - `to` (string | object | (row) => string | object) — render the entry as a link to this
|
|
104
104
|
* vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.
|
|
105
105
|
* - `linkTarget` (string) — the link's `target`, e.g. `_blank`
|
|
106
|
+
* - `id` (string) — the action's id, passed in the `action` payload
|
|
107
|
+
* - `builtin` (boolean) — set by CnIndexPage on its built-in View / Edit / Copy / Delete; a built-in's testid is `cn-action-item-<id>` in every locale and its key `builtin:<id>`
|
|
106
108
|
*
|
|
107
109
|
* A link entry still emits `action`, but its `handler` is not called: the link is the
|
|
108
110
|
* navigation. A disabled entry, or a `to` without a resolvable route, stays a button.
|
|
109
111
|
*
|
|
110
|
-
* @type {Array<{label: string, icon: object | string, handler: (targetItem: object) => void, disabled: boolean | ((targetItem: object) => boolean), visible: boolean | ((targetItem: object) => boolean), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}
|
|
112
|
+
* @type {Array<{label: string, id: string, builtin: boolean, icon: object | string, handler: (targetItem: object) => void, disabled: boolean | ((targetItem: object) => boolean), visible: boolean | ((targetItem: object) => boolean), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}
|
|
111
113
|
*/
|
|
112
114
|
actions: {
|
|
113
115
|
type: Array,
|
|
@@ -137,31 +139,13 @@ export default {
|
|
|
137
139
|
|
|
138
140
|
computed: {
|
|
139
141
|
/**
|
|
140
|
-
* Filter actions by their `visible`
|
|
141
|
-
*
|
|
142
|
+
* Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnContextMenu uses.
|
|
143
|
+
* An action without either always shows.
|
|
142
144
|
*
|
|
143
145
|
* @return {Array} Visible actions for the current row.
|
|
144
146
|
*/
|
|
145
147
|
visibleActions() {
|
|
146
|
-
return this.actions.filter((action) =>
|
|
147
|
-
// A manifest is JSON and cannot hold a function, so `visibleWhen` is
|
|
148
|
-
// the only per-row gate an app configured from one can express. Only
|
|
149
|
-
// a locally decidable condition is gated on: an endpoint/source one is
|
|
150
|
-
// not this evaluator's question, and nothing gated a row action at all
|
|
151
|
-
// before, so answering `false` to it would delete the entry outright.
|
|
152
|
-
if (action.visibleWhen
|
|
153
|
-
&& isLocallyDecidableVisibleWhen(action.visibleWhen)
|
|
154
|
-
&& evaluateVisibleWhenLocal(action.visibleWhen, this.row) === false) {
|
|
155
|
-
return false
|
|
156
|
-
}
|
|
157
|
-
if (action.visible === undefined) {
|
|
158
|
-
return true
|
|
159
|
-
}
|
|
160
|
-
if (typeof action.visible === 'function') {
|
|
161
|
-
return !!action.visible(this.row)
|
|
162
|
-
}
|
|
163
|
-
return !!action.visible
|
|
164
|
-
})
|
|
148
|
+
return this.actions.filter((action) => isRowActionVisible(action, this.row))
|
|
165
149
|
},
|
|
166
150
|
|
|
167
151
|
/**
|
|
@@ -210,7 +194,7 @@ export default {
|
|
|
210
194
|
if (action.handler && typeof action.handler === 'function') {
|
|
211
195
|
action.handler(this.row)
|
|
212
196
|
}
|
|
213
|
-
this.$emit('action',
|
|
197
|
+
this.$emit('action', rowActionPayload(action, this.row))
|
|
214
198
|
},
|
|
215
199
|
|
|
216
200
|
/**
|
|
@@ -229,26 +213,41 @@ export default {
|
|
|
229
213
|
}
|
|
230
214
|
followItemActionLink(event, link, this.$router)
|
|
231
215
|
/**
|
|
232
|
-
* @event action User picked an entry. Payload: the action's
|
|
233
|
-
* @type {{ action: string, row: object|null }}
|
|
216
|
+
* @event action User picked an entry. Payload: `action` (the label), the row, the action's `id` when it has one, and `builtin: true` for a CnIndexPage built-in. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.
|
|
217
|
+
* @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}
|
|
234
218
|
*/
|
|
235
|
-
this.$emit('action',
|
|
219
|
+
this.$emit('action', rowActionPayload(action, this.row))
|
|
236
220
|
},
|
|
237
221
|
|
|
238
222
|
/**
|
|
239
223
|
* Slugify an action label for use in stable `data-testid` selectors.
|
|
240
|
-
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
241
|
-
* `:data-testid` binding on NcActionButton — does not affect runtime
|
|
242
|
-
* behaviour or rendered text.
|
|
224
|
+
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
243
225
|
*
|
|
244
226
|
* @param {string} label - The action's display label
|
|
245
227
|
* @return {string} kebab-case slug suitable for a testid suffix.
|
|
246
228
|
*/
|
|
247
229
|
slugifyLabel(label) {
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
230
|
+
return slugifyActionLabel(label)
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.
|
|
235
|
+
*
|
|
236
|
+
* @param {object} action The action definition.
|
|
237
|
+
* @return {string} The testid.
|
|
238
|
+
*/
|
|
239
|
+
actionTestId(action) {
|
|
240
|
+
return rowActionTestId(action)
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The entry's render key: `builtin:<id>` for a built-in, the label otherwise.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} action The action definition.
|
|
247
|
+
* @return {string} The key.
|
|
248
|
+
*/
|
|
249
|
+
actionKey(action) {
|
|
250
|
+
return rowActionKey(action)
|
|
252
251
|
},
|
|
253
252
|
},
|
|
254
253
|
}
|
|
@@ -641,9 +641,15 @@ export default {
|
|
|
641
641
|
value = this.choiceLabel(step, raw)
|
|
642
642
|
}
|
|
643
643
|
} else if (step.type === 'config-fields') {
|
|
644
|
+
// Only the fields the reader filled in: a row of empty
|
|
645
|
+
// "Name: , KvK: ," reads as broken, not as "not set".
|
|
644
646
|
value = this.fieldsFor(step)
|
|
645
|
-
.
|
|
646
|
-
|
|
647
|
+
.filter((f) => {
|
|
648
|
+
const v = this.configModel[f.key]
|
|
649
|
+
return v !== null && v !== undefined && String(v).trim() !== ''
|
|
650
|
+
})
|
|
651
|
+
.map((f) => `${f.label}: ${this.configModel[f.key]}`)
|
|
652
|
+
.join(', ') || t('nextcloud-vue', 'Not set')
|
|
647
653
|
}
|
|
648
654
|
if (this.isOnDemand(step)) {
|
|
649
655
|
const ran = this.ranThisSession(step.id)
|
|
@@ -1312,7 +1312,7 @@ export default {
|
|
|
1312
1312
|
* @return {string}
|
|
1313
1313
|
*/
|
|
1314
1314
|
formattedValue() {
|
|
1315
|
-
return formatMetricValue(this.displayValue, this.content.format, this.configCtx)
|
|
1315
|
+
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null)
|
|
1316
1316
|
},
|
|
1317
1317
|
|
|
1318
1318
|
/**
|
|
@@ -1346,7 +1346,7 @@ export default {
|
|
|
1346
1346
|
return ''
|
|
1347
1347
|
}
|
|
1348
1348
|
const { prefix, suffix, ...rest } = (this.content.format || {})
|
|
1349
|
-
return formatMetricValue(this.limitValue, rest, this.configCtx)
|
|
1349
|
+
return formatMetricValue(this.limitValue, rest, this.configCtx, this.objectCtx?.object || null)
|
|
1350
1350
|
},
|
|
1351
1351
|
|
|
1352
1352
|
/**
|