@conduction/nextcloud-vue 2.66.0 → 2.68.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/CnActionsBar/CnActionsBar.vue2.js +19 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- 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/CnDataTable/CnDataTable.vue2.js +4 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/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 +89 -49
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +337 -315
- 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 +13 -13
- 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/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/composables/useDependencyCheck.js +44 -1
- package/dist/esm/composables/useDependencyCheck.js.map +1 -1
- package/dist/esm/composables/useListView.js +43 -1
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useSetupStatus.js +26 -3
- package/dist/esm/composables/useSetupStatus.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +20 -2
- package/dist/esm/store/plugins/liveUpdates.js +19 -9
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +20 -9
- package/dist/esm/store/useObjectStore.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 +1431 -802
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +20 -2
- package/l10n/en.json +4 -1
- package/l10n/nl.json +4 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +2 -2
- package/src/components/CnActionsBar/CnActionsBar.md +2 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
- package/src/components/CnAppNav/CnAppNav.vue +2 -2
- package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +4 -4
- package/src/components/CnFormDialog/CnFormDialog.vue +92 -18
- package/src/components/CnIndexPage/CnIndexPage.md +23 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +108 -52
- package/src/components/CnIndexPage/defaultActions.js +6 -7
- package/src/components/CnRowActions/CnRowActions.vue +36 -37
- package/src/components/CnSetupWizard/CnSetupWizard.vue +13 -13
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
- package/src/composables/useContextMenu.js +4 -2
- package/src/composables/useDependencyCheck.js +43 -0
- package/src/composables/useListView.js +43 -1
- package/src/composables/useSetupStatus.js +26 -3
- package/src/css/actions-bar.css +20 -2
- package/src/schemas/app-manifest-v2.schema.json +48 -10
- package/src/schemas/app-manifest.schema.json +1 -1
- package/src/store/plugins/liveUpdates.js +19 -9
- package/src/store/useObjectStore.js +19 -8
- 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
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
:showSearch="inlineSearch"
|
|
60
60
|
:searchValue="effectiveSearchValue"
|
|
61
61
|
:searchPlaceholder="searchPlaceholder"
|
|
62
|
+
:showCountWithSearch="showCountWithSearch"
|
|
62
63
|
:refreshing="effectiveRefreshing"
|
|
63
64
|
:refreshDisabled="refreshDisabled"
|
|
64
65
|
:addDisabled="addDisabled"
|
|
@@ -417,6 +418,21 @@
|
|
|
417
418
|
<NcLoadingIcon :size="32" :name="loadingText" />
|
|
418
419
|
</div>
|
|
419
420
|
|
|
421
|
+
<!-- Error state: the latest self-fetch failed (e.g. a search term the server rejects) -->
|
|
422
|
+
<div v-else-if="selfFetchFailed"
|
|
423
|
+
class="cn-index-page__empty"
|
|
424
|
+
role="status"
|
|
425
|
+
data-testid="cn-index-page-fetch-error">
|
|
426
|
+
<NcEmptyContent :name="t('nextcloud-vue', 'An error occurred')"
|
|
427
|
+
:description="effectiveSearchValue
|
|
428
|
+
? t('nextcloud-vue', 'Change the search or try again.')
|
|
429
|
+
: t('nextcloud-vue', 'Try again later.')">
|
|
430
|
+
<template #icon>
|
|
431
|
+
<AlertCircleOutline :size="64" />
|
|
432
|
+
</template>
|
|
433
|
+
</NcEmptyContent>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
420
436
|
<!-- Empty state -->
|
|
421
437
|
<div v-else-if="effectiveObjects.length === 0" class="cn-index-page__empty">
|
|
422
438
|
<slot name="empty">
|
|
@@ -676,11 +692,11 @@
|
|
|
676
692
|
</template>
|
|
677
693
|
</CnCardGrid>
|
|
678
694
|
|
|
679
|
-
<!-- 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. -->
|
|
680
696
|
<CnContextMenu
|
|
681
697
|
v-model:open="contextMenuOpen"
|
|
682
|
-
:actions="
|
|
683
|
-
:targetItem="
|
|
698
|
+
:actions="rowActionsFor(contextMenuShownRow)"
|
|
699
|
+
:targetItem="contextMenuShownRow"
|
|
684
700
|
@action="onRowAction"
|
|
685
701
|
@close="closeContextMenu" />
|
|
686
702
|
|
|
@@ -774,6 +790,7 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|
|
774
790
|
import { translate as t } from '@nextcloud/l10n'
|
|
775
791
|
import { NcActionButton, NcActionCaption, NcActionCheckbox, NcActions, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
776
792
|
import { getCurrentInstance, inject, markRaw, ref } from 'vue'
|
|
793
|
+
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
777
794
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
778
795
|
import Cog from 'vue-material-design-icons/Cog.vue'
|
|
779
796
|
import DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'
|
|
@@ -795,8 +812,10 @@ import { resolveClaimedTeams, resolveClaimTokens, splitViewsIntoTabs, viewAsTab,
|
|
|
795
812
|
import { LIST_SHORTCUTS, listPaletteCommands, shortcutFor } from '../../utils/listShortcuts.js'
|
|
796
813
|
import { multiKeySort } from '../../utils/multiKeySort.js'
|
|
797
814
|
import { resolveDeepTokens, resolveFilterValue } from '../../utils/resolveFilterTokens.js'
|
|
815
|
+
import { resolveRowActions } from '../../utils/resolveRowActions.js'
|
|
798
816
|
import { resolveFilterMap } from '../../utils/routeFilters.js'
|
|
799
817
|
import { availableRowActions, DEFAULT_ROW_ACTION_FIELD, refusalReasonFor, undeclaredRowActions } from '../../utils/rowActionAvailability.js'
|
|
818
|
+
import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.js'
|
|
800
819
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
801
820
|
import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
802
821
|
import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
@@ -1054,6 +1073,7 @@ export default {
|
|
|
1054
1073
|
NcActionCaption,
|
|
1055
1074
|
NcActionCheckbox,
|
|
1056
1075
|
NcButton,
|
|
1076
|
+
AlertCircleOutline,
|
|
1057
1077
|
Close,
|
|
1058
1078
|
Cog,
|
|
1059
1079
|
DatabaseSearch,
|
|
@@ -1669,7 +1689,11 @@ export default {
|
|
|
1669
1689
|
default: () => ({}),
|
|
1670
1690
|
},
|
|
1671
1691
|
|
|
1672
|
-
/**
|
|
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
|
+
*/
|
|
1673
1697
|
actions: {
|
|
1674
1698
|
type: Array,
|
|
1675
1699
|
default: () => [],
|
|
@@ -2034,6 +2058,17 @@ export default {
|
|
|
2034
2058
|
default: false,
|
|
2035
2059
|
},
|
|
2036
2060
|
|
|
2061
|
+
/**
|
|
2062
|
+
* Keep the "Showing X of Y" counter visible beside the inline search field,
|
|
2063
|
+
* after any `#after-search` controls (only relevant with `inlineSearch`,
|
|
2064
|
+
* which otherwise takes the counter's place). Fed from the manifest as
|
|
2065
|
+
* `pages[].config.showCountWithSearch`.
|
|
2066
|
+
*/
|
|
2067
|
+
showCountWithSearch: {
|
|
2068
|
+
type: Boolean,
|
|
2069
|
+
default: false,
|
|
2070
|
+
},
|
|
2071
|
+
|
|
2037
2072
|
/** Placeholder for the inline search field (manifest `config.searchPlaceholder`) */
|
|
2038
2073
|
searchPlaceholder: {
|
|
2039
2074
|
type: String,
|
|
@@ -2732,6 +2767,8 @@ export default {
|
|
|
2732
2767
|
folderRowLayouts: {},
|
|
2733
2768
|
/** The row the keyboard is on, or -1. */
|
|
2734
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,
|
|
2735
2772
|
/** The row a quick edit is open on, or null. */
|
|
2736
2773
|
quickEditRow: null,
|
|
2737
2774
|
/** The row as the server holds it, after a stale save. */
|
|
@@ -2934,11 +2971,23 @@ export default {
|
|
|
2934
2971
|
return this.isSelfFetch && !!this.list
|
|
2935
2972
|
},
|
|
2936
2973
|
|
|
2974
|
+
/**
|
|
2975
|
+
* Whether the latest self-fetch failed. The store keeps the previous
|
|
2976
|
+
* rows and pagination on a failed fetch, so while this holds the page
|
|
2977
|
+
* shows its error state instead of results for an earlier query.
|
|
2978
|
+
*/
|
|
2979
|
+
selfFetchFailed() {
|
|
2980
|
+
return this.isSelfFetchMode && !!this.list.error?.value
|
|
2981
|
+
},
|
|
2982
|
+
|
|
2937
2983
|
/** Rows: store collection in self-fetch mode, else the `objects` prop. */
|
|
2938
2984
|
effectiveObjects() {
|
|
2939
2985
|
if (this.isNamedSource) {
|
|
2940
2986
|
return this.namedRows
|
|
2941
2987
|
}
|
|
2988
|
+
if (this.selfFetchFailed) {
|
|
2989
|
+
return []
|
|
2990
|
+
}
|
|
2942
2991
|
return this.isSelfFetchMode ? (this.list.objects.value || []) : this.objects
|
|
2943
2992
|
},
|
|
2944
2993
|
|
|
@@ -3342,7 +3391,9 @@ export default {
|
|
|
3342
3391
|
s: this.exportSchemaSlug,
|
|
3343
3392
|
f: this.filter || null,
|
|
3344
3393
|
e: this.countEntries,
|
|
3345
|
-
|
|
3394
|
+
// The store's total, not the error override in effectivePagination,
|
|
3395
|
+
// so a failed fetch and its recovery do not refetch the counts.
|
|
3396
|
+
t: Number((this.isSelfFetchMode ? this.list.pagination.value : this.effectivePagination)?.total ?? 0),
|
|
3346
3397
|
})
|
|
3347
3398
|
},
|
|
3348
3399
|
|
|
@@ -3430,7 +3481,7 @@ export default {
|
|
|
3430
3481
|
if (this.quickEditFields.length > 0) {
|
|
3431
3482
|
handlers['row-quick-edit'] = () => this.focusedRow && this.openQuickEdit(this.focusedRow)
|
|
3432
3483
|
}
|
|
3433
|
-
if (this.focusedRow && this.
|
|
3484
|
+
if (this.focusedRow && this.primaryRowActionFor(this.focusedRow)) {
|
|
3434
3485
|
handlers['row-primary'] = () => this.runPrimaryRowAction()
|
|
3435
3486
|
}
|
|
3436
3487
|
return handlers
|
|
@@ -3748,6 +3799,9 @@ export default {
|
|
|
3748
3799
|
|
|
3749
3800
|
/** Pagination: store pagination in self-fetch mode, else the `pagination` prop. */
|
|
3750
3801
|
effectivePagination() {
|
|
3802
|
+
if (this.selfFetchFailed) {
|
|
3803
|
+
return { ...this.list.pagination.value, total: 0, page: 1, pages: 1 }
|
|
3804
|
+
}
|
|
3751
3805
|
return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
|
|
3752
3806
|
},
|
|
3753
3807
|
|
|
@@ -4108,7 +4162,8 @@ export default {
|
|
|
4108
4162
|
},
|
|
4109
4163
|
|
|
4110
4164
|
/**
|
|
4111
|
-
* 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`.
|
|
4112
4167
|
*
|
|
4113
4168
|
* REQ-MAD-3 / REQ-MAD-4 / REQ-MAD-5 / REQ-MAD-6 / REQ-MAD-7
|
|
4114
4169
|
* (manifest-actions-dispatch) — for any action whose `handler`
|
|
@@ -4124,58 +4179,41 @@ export default {
|
|
|
4124
4179
|
registry: this.effectiveRegistry,
|
|
4125
4180
|
customComponents: this.effectiveCustomComponents,
|
|
4126
4181
|
}
|
|
4127
|
-
// Drop anything that is not an action OBJECT. A bare string — the
|
|
4128
|
-
// shorthand `"edit"` an author reasonably expects to mean "show the
|
|
4129
|
-
// built-in Edit" — has no `label` and no `icon`, so it rendered as a
|
|
4130
|
-
// full-height NcActionButton with nothing in it: an invisible,
|
|
4131
|
-
// clickable, inert row in the overflow menu. Observed on dossiq,
|
|
4132
|
-
// where `config.actions: ["create", "edit", "delete", {…}]` put three
|
|
4133
|
-
// blank rows above the real ones on four different index pages.
|
|
4134
|
-
//
|
|
4135
|
-
// Named, not silently swallowed: a dropped action is a capability the
|
|
4136
|
-
// author asked for and did not get, and the manifest schema now
|
|
4137
|
-
// rejects the shape at authoring time (`config.actions` is governed by
|
|
4138
|
-
// the same `action` definition as `pages[].actions`). Built-ins are
|
|
4139
|
-
// turned on with the `show*Action` toggles, not by naming them here.
|
|
4140
4182
|
// A named source may supply its own row actions, on the same
|
|
4141
4183
|
// precedence as its columns: what the manifest declares wins, and the
|
|
4142
|
-
// source is the DEFAULT for a page that declares none.
|
|
4143
|
-
//
|
|
4144
|
-
// field of the same adapter to be wired late, after `columns` and
|
|
4145
|
-
// `addLabel`.
|
|
4146
|
-
//
|
|
4147
|
-
// Note what this still cannot do: these are MERGED with the built-ins
|
|
4148
|
-
// below, so a source can add an action but cannot say "these and no
|
|
4149
|
-
// 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.
|
|
4150
4186
|
const manifestDeclared = this.actions && this.actions.length > 0
|
|
4151
4187
|
const declaredActions = manifestDeclared
|
|
4152
4188
|
? this.actions
|
|
4153
4189
|
: ((this.isNamedSource && this.namedSource && this.namedSource.rowActions) || [])
|
|
4154
4190
|
|
|
4155
|
-
const
|
|
4156
|
-
|
|
4157
|
-
if (a && typeof a === 'object' && !Array.isArray(a)) {
|
|
4191
|
+
const { actions, warnings } = resolveRowActions(declaredActions, this.defaultActions, {
|
|
4192
|
+
prepare: (a) => {
|
|
4158
4193
|
// A SOURCE action may say `action: 'open'`: open this row the
|
|
4159
4194
|
// way a row click does — the source's own navigation
|
|
4160
4195
|
// (`openRow`/`detailRoute`), with `edit-open` emitted so a
|
|
4161
4196
|
// host that configured a row target (`config.rowRoute`) wins.
|
|
4162
|
-
// Resolved here because a source is a composable with no
|
|
4163
|
-
//
|
|
4164
|
-
// it rendered as a dead menu entry, because `action` was a
|
|
4165
|
-
// key nothing read (dispatch reads `handler` and `type`).
|
|
4166
|
-
// Source-declared actions only: the manifest grammar for
|
|
4167
|
-
// 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.
|
|
4168
4199
|
if (!manifestDeclared && a.action === 'open' && typeof a.handler !== 'function') {
|
|
4169
|
-
|
|
4170
|
-
continue
|
|
4200
|
+
return { ...a, handler: (row) => this.openSourceRow(row) }
|
|
4171
4201
|
}
|
|
4172
|
-
|
|
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') {
|
|
4173
4208
|
continue
|
|
4174
4209
|
}
|
|
4175
|
-
//
|
|
4176
|
-
|
|
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
|
+
}
|
|
4177
4215
|
}
|
|
4178
|
-
return
|
|
4216
|
+
return actions
|
|
4179
4217
|
},
|
|
4180
4218
|
|
|
4181
4219
|
hasRowActions() {
|
|
@@ -5421,9 +5459,20 @@ export default {
|
|
|
5421
5459
|
},
|
|
5422
5460
|
|
|
5423
5461
|
/**
|
|
5424
|
-
*
|
|
5425
|
-
*
|
|
5426
|
-
*
|
|
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`).
|
|
5427
5476
|
*
|
|
5428
5477
|
* @return {void}
|
|
5429
5478
|
* @spec openspec/changes/working-list-row-actions/specs/index-page/spec.md
|
|
@@ -5433,14 +5482,14 @@ export default {
|
|
|
5433
5482
|
if (!row) {
|
|
5434
5483
|
return
|
|
5435
5484
|
}
|
|
5436
|
-
const
|
|
5485
|
+
const first = this.primaryRowActionFor(row)
|
|
5437
5486
|
if (!first) {
|
|
5438
5487
|
return
|
|
5439
5488
|
}
|
|
5440
5489
|
if (typeof first.handler === 'function') {
|
|
5441
5490
|
first.handler(row)
|
|
5442
5491
|
}
|
|
5443
|
-
this
|
|
5492
|
+
this.onRowAction(rowActionPayload(first, row))
|
|
5444
5493
|
},
|
|
5445
5494
|
|
|
5446
5495
|
/**
|
|
@@ -5757,16 +5806,22 @@ export default {
|
|
|
5757
5806
|
},
|
|
5758
5807
|
|
|
5759
5808
|
/**
|
|
5760
|
-
* Intercepts CnRowActions' bubbled `@action` so `handler: "none"`
|
|
5761
|
-
*
|
|
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.
|
|
5762
5811
|
*
|
|
5763
|
-
* @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.
|
|
5764
5813
|
*/
|
|
5765
5814
|
onRowAction(payload) {
|
|
5766
|
-
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)
|
|
5767
5818
|
if (matched && matched._dispatchSuppress) {
|
|
5768
5819
|
return
|
|
5769
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
|
+
*/
|
|
5770
5825
|
this.$emit('action', payload)
|
|
5771
5826
|
},
|
|
5772
5827
|
|
|
@@ -6712,6 +6767,7 @@ export default {
|
|
|
6712
6767
|
// --- Context menu handlers ---
|
|
6713
6768
|
|
|
6714
6769
|
onRowContextMenu({ row, event }) {
|
|
6770
|
+
this.contextMenuShownRow = row
|
|
6715
6771
|
this.openContextMenu({ item: row, event })
|
|
6716
6772
|
},
|
|
6717
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
|
}
|
|
@@ -199,13 +199,12 @@
|
|
|
199
199
|
|
|
200
200
|
<script>
|
|
201
201
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
202
|
-
import { loadState } from '@nextcloud/initial-state'
|
|
203
202
|
import { translate as t } from '@nextcloud/l10n'
|
|
204
203
|
import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'
|
|
205
204
|
import CnChoiceCards from '../CnChoiceCards/CnChoiceCards.vue'
|
|
206
205
|
import CnLeafDependencySettings from '../CnLeafDependencySettings/CnLeafDependencySettings.vue'
|
|
207
206
|
import CnWizardDialog from '../CnWizardDialog/CnWizardDialog.vue'
|
|
208
|
-
import { checkDependencies, isDependencyResolved } from '../../composables/useDependencyCheck.js'
|
|
207
|
+
import { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses } from '../../composables/useDependencyCheck.js'
|
|
209
208
|
import { useSetupStatus } from '../../composables/useSetupStatus.js'
|
|
210
209
|
import { fieldsFromSchema } from '../../utils/schema.js'
|
|
211
210
|
|
|
@@ -474,16 +473,15 @@ export default {
|
|
|
474
473
|
*
|
|
475
474
|
* @return {Array<{ step: object, missing: Array<string> }>}
|
|
476
475
|
* @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
|
|
476
|
+
* @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
|
|
477
477
|
*/
|
|
478
478
|
skippedSteps() {
|
|
479
479
|
return this.declaredSteps
|
|
480
480
|
.filter((s) => Array.isArray(s.requires) && s.requires.length > 0)
|
|
481
481
|
.map((step) => {
|
|
482
482
|
const known = Object.fromEntries(this.dependencyRows.map((d) => [d.id, d]))
|
|
483
|
-
const missing =
|
|
484
|
-
.map((row) => known[row.id] || row)
|
|
485
|
-
.filter((row) => !isDependencyResolved(row))
|
|
486
|
-
.map((row) => row.name || row.id)
|
|
483
|
+
const missing = missingRequiredApps(step.requires, this.serverAppStatuses)
|
|
484
|
+
.map((row) => (known[row.id] || row).name || row.id)
|
|
487
485
|
return { step, missing }
|
|
488
486
|
})
|
|
489
487
|
.filter((entry) => entry.missing.length > 0)
|
|
@@ -523,11 +521,7 @@ export default {
|
|
|
523
521
|
* @return {object}
|
|
524
522
|
*/
|
|
525
523
|
serverAppStatuses() {
|
|
526
|
-
|
|
527
|
-
return loadState(this.appId, 'dependency_statuses', {}) || {}
|
|
528
|
-
} catch {
|
|
529
|
-
return {}
|
|
530
|
-
}
|
|
524
|
+
return readServerAppStatuses(this.appId)
|
|
531
525
|
},
|
|
532
526
|
|
|
533
527
|
/**
|
|
@@ -647,9 +641,15 @@ export default {
|
|
|
647
641
|
value = this.choiceLabel(step, raw)
|
|
648
642
|
}
|
|
649
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".
|
|
650
646
|
value = this.fieldsFor(step)
|
|
651
|
-
.
|
|
652
|
-
|
|
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')
|
|
653
653
|
}
|
|
654
654
|
if (this.isOnDemand(step)) {
|
|
655
655
|
const ran = this.ranThisSession(step.id)
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<script>
|
|
54
54
|
import { getLanguage, translate as t } from '@nextcloud/l10n'
|
|
55
55
|
import { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
|
|
56
|
-
import { auditEvents, fieldEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'
|
|
56
|
+
import { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* CnTimelineWidget — an object's dated events in time order (`timeline`
|
|
@@ -193,6 +193,7 @@ export default {
|
|
|
193
193
|
const now = new Date()
|
|
194
194
|
const all = [
|
|
195
195
|
...fieldEvents(this.resolvedObject || {}, this.fieldsConfig, now),
|
|
196
|
+
...listEvents(this.resolvedObject || {}, this.content.lists, now),
|
|
196
197
|
...this.relatedConfig.flatMap((cfg, i) => relatedEvents(this.relatedRows[i] || [], cfg, now)),
|
|
197
198
|
...auditEvents(this.auditEntries, this.describeAudit, now),
|
|
198
199
|
...timelineEntryEvents(this.timelineEntries, t('nextcloud-vue', 'Note'), now),
|
|
@@ -347,7 +348,7 @@ export default {
|
|
|
347
348
|
})
|
|
348
349
|
|
|
349
350
|
if (this.content.auditTrail === true) {
|
|
350
|
-
jobs.push(axios.get(generateUrl(`${base}/audit-
|
|
351
|
+
jobs.push(axios.get(generateUrl(`${base}/audit-trails`), { params: { limit: this.content.auditLimit || 50 } })
|
|
351
352
|
.then((r) => {
|
|
352
353
|
const data = r && r.data
|
|
353
354
|
this.auditEntries = (data && (data.results || data)) || []
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { onBeforeUnmount, ref } from 'vue'
|
|
2
|
+
import { rowActionPayload } from '../utils/rowActionItem.js'
|
|
2
3
|
|
|
3
4
|
export const CTX_MENU_CSS_VAR_X = '--cn-ctx-menu-x'
|
|
4
5
|
export const CTX_MENU_CSS_VAR_Y = '--cn-ctx-menu-y'
|
|
@@ -135,15 +136,16 @@ export function useContextMenu() {
|
|
|
135
136
|
*
|
|
136
137
|
* Calls `action.handler(targetItem)` if a handler exists, then returns a
|
|
137
138
|
* payload object the caller can pass to `$emit('action', payload)`.
|
|
139
|
+
* The payload is the one CnRowActions and CnContextMenu emit: the label as `action`, plus `id` and `builtin: true` when the action carries them.
|
|
138
140
|
*
|
|
139
141
|
* @param {object} action Action definition with `label` and optional `handler`
|
|
140
|
-
* @return {{ action: string, row: object }}
|
|
142
|
+
* @return {{ action: string, row: object, id?: string, builtin?: boolean }}
|
|
141
143
|
*/
|
|
142
144
|
function triggerAction(action) {
|
|
143
145
|
if (action.handler && typeof action.handler === 'function') {
|
|
144
146
|
action.handler(targetItem.value)
|
|
145
147
|
}
|
|
146
|
-
return
|
|
148
|
+
return rowActionPayload(action, targetItem.value)
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
// Clean up DOM if the consumer unmounts while the menu is open — nothing
|