@conduction/nextcloud-vue 2.106.0 → 2.107.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.
Files changed (135) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +9 -0
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js.map +1 -0
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +338 -0
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -0
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +249 -0
  6. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js +2 -2
  8. package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +145 -138
  11. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +102 -2
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +83 -6
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +4 -2
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  21. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +389 -20
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -35
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  26. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -2
  27. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  28. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +69 -5
  29. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +38 -5
  31. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  32. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  33. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +7 -1
  34. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +8 -0
  37. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -0
  38. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +297 -0
  39. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -0
  40. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +24 -0
  41. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -0
  42. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js +9 -0
  43. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js.map +1 -0
  44. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js +95 -0
  45. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js.map +1 -0
  46. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js +123 -0
  47. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js.map +1 -0
  48. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js +1 -1
  49. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js +13 -5
  50. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js.map +1 -1
  51. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js +3 -1
  52. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js.map +1 -1
  53. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue.js +1 -1
  54. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js +49 -23
  55. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js.map +1 -1
  56. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js +74 -27
  57. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js.map +1 -1
  58. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js +0 -1
  59. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js.map +1 -1
  60. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +18 -413
  61. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  62. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +27 -214
  63. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  64. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js +9 -0
  65. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js.map +1 -0
  66. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js +190 -0
  67. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js.map +1 -0
  68. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js +270 -0
  69. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js.map +1 -0
  70. package/dist/esm/index.js +4 -0
  71. package/dist/esm/index.js.map +1 -1
  72. package/dist/esm/l10n/en.json.js +56 -1
  73. package/dist/esm/l10n/en.json.js.map +1 -1
  74. package/dist/esm/l10n/nl.json.js +56 -1
  75. package/dist/esm/l10n/nl.json.js.map +1 -1
  76. package/dist/esm/library-style.js +1 -1
  77. package/dist/esm/mixins/savedViewsList.js +235 -0
  78. package/dist/esm/mixins/savedViewsList.js.map +1 -0
  79. package/dist/esm/nextcloud-vue.css +348 -108
  80. package/dist/esm/utils/actionsDispatcher.js +2 -2
  81. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  82. package/dist/esm/utils/builtInModals.js +39 -0
  83. package/dist/esm/utils/builtInModals.js.map +1 -0
  84. package/dist/esm/utils/mapMarkerColor.js +121 -0
  85. package/dist/esm/utils/mapMarkerColor.js.map +1 -0
  86. package/dist/esm/utils/mergeManifestDelta.js +4 -0
  87. package/dist/esm/utils/mergeManifestDelta.js.map +1 -1
  88. package/dist/esm/utils/savedViewHelpers.js +59 -7
  89. package/dist/esm/utils/savedViewHelpers.js.map +1 -1
  90. package/dist/esm/utils/savedViewMenu.js +154 -0
  91. package/dist/esm/utils/savedViewMenu.js.map +1 -0
  92. package/dist/esm/utils/searchGroupSharees.js +50 -15
  93. package/dist/esm/utils/searchGroupSharees.js.map +1 -1
  94. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  95. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  96. package/dist/nextcloud-vue.cjs.js +13351 -10855
  97. package/dist/nextcloud-vue.cjs.js.map +2 -2
  98. package/dist/nextcloud-vue.css +348 -108
  99. package/l10n/en.json +56 -1
  100. package/l10n/nl.json +56 -1
  101. package/package.json +1 -1
  102. package/src/components/CnActionDialog/CnActionDialog.vue +545 -0
  103. package/src/components/CnActionDialog/index.js +12 -0
  104. package/src/components/CnAppNav/CnAppNav.vue +17 -0
  105. package/src/components/CnAppRoot/CnAppRoot.vue +102 -2
  106. package/src/components/CnDashboardPage/CnDashboardPage.vue +86 -7
  107. package/src/components/CnIndexPage/CnIndexPage.vue +409 -23
  108. package/src/components/CnIndexPage/useSelfFetchList.js +21 -1
  109. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  110. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +91 -1
  111. package/src/components/CnMapWidget/CnMapWidget.vue +16 -1
  112. package/src/components/CnPartyPicker/CnPartyPicker.vue +317 -0
  113. package/src/components/CnPartyPicker/index.js +11 -0
  114. package/src/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue +192 -0
  115. package/src/components/CnSavedViewEditDialog/index.js +11 -0
  116. package/src/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue +21 -6
  117. package/src/components/CnSavedViewShareFields/CnSavedViewShareFields.vue +130 -37
  118. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +36 -599
  119. package/src/components/CnSavedViewsPanel/CnSavedViewsPanel.vue +459 -0
  120. package/src/components/CnSavedViewsPanel/index.js +11 -0
  121. package/src/components/index.js +4 -0
  122. package/src/css/index-page.css +16 -0
  123. package/src/css/look-board-index.css +5 -3
  124. package/src/css/look-board.css +18 -3
  125. package/src/index.js +4 -0
  126. package/src/mixins/savedViewsList.js +231 -0
  127. package/src/schemas/app-manifest-v2.schema.json +98 -3
  128. package/src/utils/actionsDispatcher.js +2 -2
  129. package/src/utils/builtInModals.js +35 -0
  130. package/src/utils/mapMarkerColor.js +117 -0
  131. package/src/utils/mergeManifestDelta.js +4 -0
  132. package/src/utils/savedViewHelpers.js +58 -6
  133. package/src/utils/savedViewMenu.js +150 -0
  134. package/src/utils/searchGroupSharees.js +49 -14
  135. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -14,8 +14,8 @@ import ImportIcon from 'vue-material-design-icons/Import.vue';
14
14
  import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue';
15
15
  import Tune from 'vue-material-design-icons/Tune.vue';
16
16
  import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue';
17
- import __sfc_main$y from '../../dialogs/CnConfirmDialog.vue.js';
18
- import __sfc_main$z from '../../dialogs/CnQuickEditDialog.vue.js';
17
+ import __sfc_main$z from '../../dialogs/CnConfirmDialog.vue.js';
18
+ import __sfc_main$A from '../../dialogs/CnQuickEditDialog.vue.js';
19
19
  import __sfc_main$2 from '../CnBuildiqEditButton/CnBuildiqEditButton.vue.js';
20
20
  import __sfc_main$4 from '../CnEmptyContent/CnEmptyContent.vue.js';
21
21
  import __sfc_main$3 from '../CnFollowToggle/CnFollowToggle.vue.js';
@@ -41,12 +41,13 @@ import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js';
41
41
  import { resolveClaimedTeams, splitViewsIntoTabs, viewAsTab, resolveClaimTokens, viewIdOf } from '../../utils/listLenses.js';
42
42
  import { LIST_SHORTCUTS, listPaletteCommands, shortcutFor } from '../../utils/listShortcuts.js';
43
43
  import { mapListItems } from '../../utils/mapList.js';
44
+ import { resolveCssColor, markerColorOf, normalizeMarkerColor } from '../../utils/mapMarkerColor.js';
44
45
  import { multiKeySort } from '../../utils/multiKeySort.js';
45
46
  import { fetchGroups } from '../../utils/nextcloudDirectory.js';
46
47
  import { OBJECTS_CHANGED_EVENT } from '../../utils/objectSignals.js';
47
48
  import { objectUrlOf, patchObject } from '../../utils/objectWrite.js';
48
49
  import { withPersonalLenses } from '../../utils/personalLenses.js';
49
- import { resolveDeepTokens, resolveFilterValue } from '../../utils/resolveFilterTokens.js';
50
+ import { resolveDeepTokens, resolveFilterValue, resolveFilterTokens } from '../../utils/resolveFilterTokens.js';
50
51
  import { resolveRowActions } from '../../utils/resolveRowActions.js';
51
52
  import { resolveFilterMap } from '../../utils/routeFilters.js';
52
53
  import { DEFAULT_ROW_ACTION_FIELD, withoutViewWhenRowOpensDetail, availableRowActions, refusalReasonFor, undeclaredRowActions } from '../../utils/rowActionAvailability.js';
@@ -54,7 +55,7 @@ import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.
54
55
  import { isNewTabClick, preventMiddleClickAutoscroll, isRowMiddleClick, isNewTabHandled, markNewTabHandled } from '../../utils/rowAuxClick.js';
55
56
  import { compileRowClass } from '../../utils/rowClassRules.js';
56
57
  import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js';
57
- import { viewMatchesScope, extractViewState, buildRouteQueryFromViewState, extractViewStateFromRouteQuery, buildViewCreatePayload, savedViewScope, normalizeSharedWith } from '../../utils/savedViewHelpers.js';
58
+ import { defaultFilterViews, viewMatchesScope, normalizeSharedWith, extractViewState, buildRouteQueryFromViewState, extractViewStateFromRouteQuery, buildViewCreatePayload, savedViewScope } from '../../utils/savedViewHelpers.js';
58
59
  import { fieldsFromSchema, columnsFromSchema } from '../../utils/schema.js';
59
60
  import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js';
60
61
  import { orderToSortKeys } from '../../utils/sortOrder.js';
@@ -87,10 +88,11 @@ import __sfc_main$5 from '../CnPageHeader/CnPageHeader.vue.js';
87
88
  import __sfc_main$i from '../CnPagination/CnPagination.vue.js';
88
89
  import __sfc_main$6 from '../CnQuickFilterBar/CnQuickFilterBar.vue.js';
89
90
  import __sfc_main$j from '../CnRowActions/CnRowActions.vue.js';
91
+ import __sfc_main$w from '../CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js';
90
92
  import __sfc_main$v from '../CnSavedViewPresentationDialog/CnSavedViewPresentationDialog.vue.js';
91
93
  import __sfc_main$u from '../CnSavedViewsControl/CnSavedViewsControl.vue.js';
92
- import __sfc_main$w from '../CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js';
93
- import __sfc_main$x from '../CnSaveViewDialog/CnSaveViewDialog.vue.js';
94
+ import __sfc_main$x from '../CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js';
95
+ import __sfc_main$y from '../CnSaveViewDialog/CnSaveViewDialog.vue.js';
94
96
  import { applyAiContext } from './aiContext.js';
95
97
  import { buildDefaultActions } from './defaultActions.js';
96
98
  import { dispatchAction } from './manifestActionDispatch.js';
@@ -99,7 +101,7 @@ import { reconcilePersonalColumns, orderColumns, personalColumnsKey } from './pe
99
101
  import { createSelfModeActions, resolveRowTarget } from './selfModeActions.js';
100
102
  import { applyRowPatches, splitLayoutFor, normalisePaneWidth, rowIdOf } from './splitView.js';
101
103
  import { useNamedSource } from './useNamedSource.js';
102
- import { useSelfFetchList, resolveInitialQuickFilterIndex, BOARD_PAGE_SIZE } from './useSelfFetchList.js';
104
+ import { useSelfFetchList, mapPlotAllLimitOf, resolveInitialQuickFilterIndex, BOARD_PAGE_SIZE } from './useSelfFetchList.js';
103
105
  import { useContextMenu } from '../../composables/useContextMenu.js';
104
106
 
105
107
  /** Rows fetched for one month in calendar mode. */
@@ -382,10 +384,11 @@ var script = {
382
384
  CnIndexSidebar: __sfc_main$t,
383
385
  CnSavedViewsControl: __sfc_main$u,
384
386
  CnSavedViewPresentationDialog: __sfc_main$v,
385
- CnSavedViewShareDialog: __sfc_main$w,
386
- CnSaveViewDialog: __sfc_main$x,
387
- CnConfirmDialog: __sfc_main$y,
388
- CnQuickEditDialog: __sfc_main$z,
387
+ CnSavedViewEditDialog: __sfc_main$w,
388
+ CnSavedViewShareDialog: __sfc_main$x,
389
+ CnSaveViewDialog: __sfc_main$y,
390
+ CnConfirmDialog: __sfc_main$z,
391
+ CnQuickEditDialog: __sfc_main$A,
389
392
  },
390
393
 
391
394
  /**
@@ -453,6 +456,12 @@ var script = {
453
456
  * simply has no held order instead of failing to render.
454
457
  */
455
458
  cnUserPreferences: { default: null },
459
+ /**
460
+ * The saved views this user put in the app menu, held by CnAppRoot:
461
+ * `{ entries, toggle(entry) }`. Absent without the shell, and then
462
+ * the sidebar offers no Add to menu.
463
+ */
464
+ cnSavedViewMenu: { default: null },
456
465
  /**
457
466
  * Consumer translation function, provided by CnAppRoot as
458
467
  * `cnTranslate: this.translate` (bound to the host app's id). Column and
@@ -1635,6 +1644,17 @@ var script = {
1635
1644
  default: '',
1636
1645
  },
1637
1646
 
1647
+ /**
1648
+ * Views the app ships for this page (`config.defaultSavedViews`), each
1649
+ * `{ slug, name, filters, search?, sort?, label? }`. Listed read-only
1650
+ * above the user's own saved views, never stored; a user may apply one
1651
+ * or save a copy. `@`-tokens in `filters` resolve when it is applied.
1652
+ */
1653
+ defaultSavedViews: {
1654
+ type: Array,
1655
+ default: () => [],
1656
+ },
1657
+
1638
1658
  /** Property name used to display item names in dialogs */
1639
1659
  massActionNameField: {
1640
1660
  type: String,
@@ -2742,6 +2762,10 @@ var script = {
2742
2762
  headerMetaCountValues: {},
2743
2763
  /** The row the pointer is on in the map list, by key; '' for none. */
2744
2764
  activeMapKey: '',
2765
+ /** The rows `mapConfig.markerColor.source` points at, or null before they load. */
2766
+ mapColorRefs: null,
2767
+ /** The page size to restore when leaving a map that plots every row. */
2768
+ mapPrevPageSize: this.viewMode === 'map' && mapPlotAllLimitOf(this.mapConfig) > 0 ? ((this.pagination && this.pagination.limit) || 20) : null,
2745
2769
  /** The tabs `quickFiltersFrom` produced, or null before they load. */
2746
2770
  generatedQuickFilters: null,
2747
2771
  /** Count per saved-view id, for the views control; null = none. */
@@ -2809,6 +2833,8 @@ var script = {
2809
2833
  // content (table / cards) starts at the top and fills the width;
2810
2834
  // opened on demand via the actions-bar toggle.
2811
2835
  sidebarOpen: false,
2836
+ /** The sidebar tab to show; follows the user's own tab changes. */
2837
+ sidebarActiveTab: '',
2812
2838
  // Saved views (saved-views-ui): the fetched view list, its
2813
2839
  // loading flag, the save-dialog toggle, and the view awaiting
2814
2840
  // delete confirmation.
@@ -2818,6 +2844,8 @@ var script = {
2818
2844
  viewPendingDelete: null,
2819
2845
  /** The own view whose audience is being changed (CnSavedViewShareDialog), or null. */
2820
2846
  viewPendingShare: null,
2847
+ /** The own view being renamed or re-filtered (CnSavedViewEditDialog), or null. */
2848
+ viewPendingEdit: null,
2821
2849
  /** The view whose presentation is being changed (CnSavedViewPresentationDialog), or null. */
2822
2850
  viewPendingPresentation: null,
2823
2851
  // Split view (case-page-and-list-as-a-place). `splitRowPatches` holds
@@ -3989,7 +4017,63 @@ var script = {
3989
4017
  * @return {Array<object>} The views.
3990
4018
  */
3991
4019
  viewsForControl() {
3992
- return this.splitSavedViews.control
4020
+ return [...this.defaultFilterViews, ...this.splitSavedViews.control]
4021
+ },
4022
+
4023
+ /**
4024
+ * What the sidebar's saved-views panel shows, or null when the page
4025
+ * does not offer saved views.
4026
+ *
4027
+ * @return {object|null}
4028
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-6
4029
+ */
4030
+ savedViewsForSidebar() {
4031
+ if (!this.allowSavedViews) {
4032
+ return null
4033
+ }
4034
+ return {
4035
+ views: this.viewsForControl,
4036
+ loading: this.savedViewsLoading,
4037
+ currentUserId: this.currentSavedViewsUserId,
4038
+ counts: this.savedViewCounts,
4039
+ selectedViewId: this.appliedSavedViewId,
4040
+ menuIds: this.savedViewMenuIds,
4041
+ }
4042
+ },
4043
+
4044
+ /**
4045
+ * The route name this page's menu entries point at.
4046
+ *
4047
+ * @return {string}
4048
+ */
4049
+ savedViewPage() {
4050
+ return (this.$route && typeof this.$route.name === 'string') ? this.$route.name : ''
4051
+ },
4052
+
4053
+ /**
4054
+ * The ids of this page's views in the app menu, or null when there is
4055
+ * no menu to add to.
4056
+ *
4057
+ * @return {Array<string>|null}
4058
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
4059
+ */
4060
+ savedViewMenuIds() {
4061
+ const menu = this.cnSavedViewMenu;
4062
+ if (!menu || typeof menu.toggle !== 'function' || this.savedViewPage === '') {
4063
+ return null
4064
+ }
4065
+ return (menu.entries || []).filter((e) => e.page === this.savedViewPage).map((e) => String(e.id))
4066
+ },
4067
+
4068
+ /**
4069
+ * The page's `defaultSavedViews` as read-only views.
4070
+ *
4071
+ * @return {Array<object>} The views.
4072
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4
4073
+ */
4074
+ defaultFilterViews() {
4075
+ const defaults = (Array.isArray(this.defaultSavedViews) ? this.defaultSavedViews : []).map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry));
4076
+ return defaultFilterViews(defaults, this.savedViewsPage)
3993
4077
  },
3994
4078
 
3995
4079
  /**
@@ -4645,11 +4729,13 @@ var script = {
4645
4729
  if (!geometry) {
4646
4730
  continue
4647
4731
  }
4732
+ const markerColor = resolveCssColor(markerColorOf(row, this.mapMarkerColor, this.mapColorRefsById));
4648
4733
  features.push({
4649
4734
  type: 'Feature',
4650
4735
  geometry,
4651
4736
  properties: {
4652
4737
  [this.rowKey]: row[this.rowKey],
4738
+ ...(markerColor ? { markerColor } : {}),
4653
4739
  ...(this.mapConfig.popupField ? { [this.mapConfig.popupField]: row[this.mapConfig.popupField] } : {}),
4654
4740
  ...(numbers && numbers.has(row) ? { markerNumber: numbers.get(row), markerActive: this.activeMapKey === String(row[this.rowKey]) } : {}),
4655
4741
  },
@@ -4678,6 +4764,80 @@ var script = {
4678
4764
  return this.mapConfig.clustering !== false
4679
4765
  },
4680
4766
 
4767
+ /**
4768
+ * The marker colour rule (`mapConfig.markerColor`), or null.
4769
+ *
4770
+ * @return {object|null} The normalised rule.
4771
+ * @spec openspec/changes/index-map-parity/specs/index-page/spec.md#requirement-a-marker-can-take-its-colour-from-a-field
4772
+ */
4773
+ mapMarkerColor() {
4774
+ return normalizeMarkerColor(this.mapConfig && this.mapConfig.markerColor)
4775
+ },
4776
+
4777
+ /** @return {Map<string, object>} The marker colour source rows by id. */
4778
+ mapColorRefsById() {
4779
+ return new Map((this.mapColorRefs || []).map((row) => [refIdOf(row), row]))
4780
+ },
4781
+
4782
+ /** @return {string} Changes when the marker colour source must be read. */
4783
+ mapColorFetchKey() {
4784
+ const source = this.mapMarkerColor && this.mapMarkerColor.source;
4785
+ if (this.currentViewMode !== 'map' || !source) {
4786
+ return ''
4787
+ }
4788
+ return [this.mapMarkerColor.field, source.register || '', source.schema || '', this.effectiveSchema ? 1 : 0].join('|')
4789
+ },
4790
+
4791
+ /**
4792
+ * How many rows the map plots when it plots every matching row
4793
+ * (`mapConfig.plotAll`: `true` for 500, or a
4794
+ * number), 0 when it plots the current page.
4795
+ *
4796
+ * @return {number} The limit.
4797
+ * @spec openspec/changes/index-map-parity/specs/index-page/spec.md#requirement-the-map-can-plot-every-matching-row
4798
+ */
4799
+ mapPlotAllLimit() {
4800
+ return mapPlotAllLimitOf(this.mapConfig)
4801
+ },
4802
+
4803
+ /** @return {number} The rows that match the filters. */
4804
+ mapMatchTotal() {
4805
+ return Math.max(this.displayObjects.length, Number(this.effectivePagination?.total || 0))
4806
+ },
4807
+
4808
+ /**
4809
+ * The count line under the map (`mapConfig.count`): `true` for "Showing
4810
+ * {located} of {total} located", or a template with `{located}` and
4811
+ * `{total}` through the host's translate. '' when not asked for.
4812
+ *
4813
+ * @return {string} The line.
4814
+ * @spec openspec/changes/index-map-parity/specs/index-page/spec.md#requirement-the-map-can-say-how-many-rows-it-placed
4815
+ */
4816
+ mapCountLine() {
4817
+ const count = this.mapConfig && this.mapConfig.count;
4818
+ if (this.currentViewMode !== 'map' || !count) {
4819
+ return ''
4820
+ }
4821
+ const vars = { located: this.mapMarkers.features.length, total: this.mapMatchTotal };
4822
+ if (typeof count === 'string' && count !== '') {
4823
+ return this.cnTranslate(count).replace(/\{(located|total)\}/g, (_, key) => String(vars[key]))
4824
+ }
4825
+ return translate('nextcloud-vue', 'Showing {located} of {total} located', vars)
4826
+ },
4827
+
4828
+ /**
4829
+ * The note when a map that plots every row hit its limit.
4830
+ *
4831
+ * @return {string} The note, or ''.
4832
+ */
4833
+ mapCapNote() {
4834
+ const limit = this.mapPlotAllLimit;
4835
+ if (this.currentViewMode !== 'map' || limit === 0 || this.mapMatchTotal <= this.displayObjects.length) {
4836
+ return ''
4837
+ }
4838
+ return translate('nextcloud-vue', 'The map shows the first {limit} of {total}. Narrow the filters to see the rest.', { limit: this.displayObjects.length, total: this.mapMatchTotal })
4839
+ },
4840
+
4681
4841
  /** @return {number|undefined} The map's initial zoom (`mapConfig.zoom`), when a number. */
4682
4842
  mapZoom() {
4683
4843
  const zoom = this.mapConfig && this.mapConfig.zoom;
@@ -5029,7 +5189,14 @@ var script = {
5029
5189
  if (!this.viewPendingDelete) {
5030
5190
  return ''
5031
5191
  }
5032
- return translate('nextcloud-vue', 'Delete the view "{name}"? This cannot be undone.', { name: this.viewPendingDelete.name })
5192
+ const view = this.viewPendingDelete;
5193
+ const parts = [translate('nextcloud-vue', 'The view "{name}" leaves your list and the menu on the left.', { name: view.name })];
5194
+ const audience = normalizeSharedWith(view.sharedWith).map((e) => e.user || e.group);
5195
+ if (audience.length > 0) {
5196
+ parts.push(translate('nextcloud-vue', 'It is shared with {names}; it leaves their lists too.', { names: audience.join(', ') }));
5197
+ }
5198
+ parts.push(translate('nextcloud-vue', 'The items themselves stay. This cannot be undone.'));
5199
+ return parts.join(' ')
5033
5200
  },
5034
5201
 
5035
5202
  /** Sort key / order: list state in self-fetch mode, else the props. */
@@ -6078,12 +6245,17 @@ var script = {
6078
6245
  /**
6079
6246
  * Effective sidebar configuration. Returns the sidebar config object
6080
6247
  * when `sidebar.enabled === true`, otherwise an `{ enabled: false }`
6081
- * stub so the embedded CnIndexSidebar is not mounted.
6248
+ * stub so the embedded CnIndexSidebar is not mounted. A page that
6249
+ * offers saved views gets the sidebar unless it switched it off
6250
+ * with `enabled: false`: the saved views live in its Search tab.
6082
6251
  */
6083
6252
  resolvedSidebar() {
6084
- if (this.sidebar && this.sidebar.enabled !== false) {
6253
+ if (this.sidebar && this.sidebar.enabled !== false && (this.sidebar.enabled || !this.allowSavedViews)) {
6085
6254
  return this.sidebar
6086
6255
  }
6256
+ if (this.allowSavedViews && !(this.sidebar && this.sidebar.enabled === false)) {
6257
+ return { ...(this.sidebar || {}), enabled: true }
6258
+ }
6087
6259
  return { enabled: false }
6088
6260
  },
6089
6261
 
@@ -6163,6 +6335,8 @@ var script = {
6163
6335
  pinnedCount: this.pinnedColumnCount,
6164
6336
  sortOptions: this.sidebarSortOptions,
6165
6337
  currentSort: this.currentSortKeys,
6338
+ savedViews: this.savedViewsForSidebar,
6339
+ activeTab: this.sidebarActiveTab,
6166
6340
  ...this.sidebarSearchProps,
6167
6341
  }
6168
6342
  },
@@ -6216,12 +6390,25 @@ var script = {
6216
6390
  this.applyViewModeFromRoute();
6217
6391
  },
6218
6392
 
6393
+ '$route.query.savedView': function() {
6394
+ this.applySavedViewFromRoute();
6395
+ },
6396
+
6219
6397
  routeViewModes() {
6220
6398
  if (this.routeViewPending) {
6221
6399
  this.applyViewModeFromRoute();
6222
6400
  }
6223
6401
  },
6224
6402
 
6403
+ mapColorFetchKey: {
6404
+ immediate: true,
6405
+ handler(key) {
6406
+ if (key !== '') {
6407
+ this.loadMapColorSource();
6408
+ }
6409
+ },
6410
+ },
6411
+
6225
6412
  boardFetchKey: {
6226
6413
  immediate: true,
6227
6414
  handler(key) {
@@ -6342,6 +6529,11 @@ var script = {
6342
6529
  listSize = this.boardPrevPageSize || 20;
6343
6530
  this.boardPrevPageSize = null;
6344
6531
  }
6532
+ const leftMapPlotAll = previous === 'map' && this.mapPrevPageSize !== null;
6533
+ if (leftMapPlotAll) {
6534
+ listSize = this.mapPrevPageSize;
6535
+ this.mapPrevPageSize = null;
6536
+ }
6345
6537
  let size = null;
6346
6538
  if (mode === 'calendar') {
6347
6539
  this.calendarPrevPageSize = listSize;
@@ -6349,7 +6541,10 @@ var script = {
6349
6541
  } else if (mode === 'board') {
6350
6542
  this.boardPrevPageSize = listSize;
6351
6543
  size = BOARD_PAGE_SIZE;
6352
- } else if (previous === 'calendar' || previous === 'board') {
6544
+ } else if (mode === 'map' && this.mapPlotAllLimit > 0 && !this.isNamedSource) {
6545
+ this.mapPrevPageSize = listSize;
6546
+ size = this.mapPlotAllLimit;
6547
+ } else if (previous === 'calendar' || previous === 'board' || leftMapPlotAll) {
6353
6548
  size = listSize;
6354
6549
  }
6355
6550
  if (size !== null && typeof this.list.onPageSizeChange === 'function') {
@@ -7756,13 +7951,21 @@ var script = {
7756
7951
  * (same resulting path/query) is swallowed.
7757
7952
  *
7758
7953
  * @param {{filters?: object, search?: string, sortKey?: ?string, sortOrder?: string, sortKeys?: Array<{key: string, order: string}>}} state Current view state.
7954
+ * @param {{savedView?: string}} [options] The saved view the address keeps naming, or none.
7759
7955
  * @return {void}
7760
7956
  */
7761
- persistViewStateToRoute(state) {
7957
+ persistViewStateToRoute(state, { savedView = '' } = {}) {
7762
7958
  if (!this.$router || !this.$route) {
7763
7959
  return
7764
7960
  }
7765
7961
  const query = { ...this.$route.query };
7962
+ // The menu entry of a saved view is active while the address names
7963
+ // it; any other change to the filters is no longer that view.
7964
+ if (savedView !== '') {
7965
+ query.savedView = savedView;
7966
+ } else {
7967
+ delete query.savedView;
7968
+ }
7766
7969
  for (const key of this.persistedFilterKeys) {
7767
7970
  delete query[key];
7768
7971
  }
@@ -7839,6 +8042,30 @@ var script = {
7839
8042
  this.$emit('clear-filters');
7840
8043
  },
7841
8044
 
8045
+ /**
8046
+ * Read the rows `mapConfig.markerColor.source` points at, once.
8047
+ *
8048
+ * @return {Promise<void>}
8049
+ */
8050
+ async loadMapColorSource() {
8051
+ const rule = this.mapMarkerColor;
8052
+ if (!rule || !rule.source || this.mapColorRefs !== null) {
8053
+ return
8054
+ }
8055
+ const property = this.effectiveSchema?.properties?.[rule.field] || null;
8056
+ const target = referenceTargetOf(rule.source, { register: this.register, property });
8057
+ if (!target.register || !target.schema) {
8058
+ return
8059
+ }
8060
+ let rows;
8061
+ try {
8062
+ rows = await fetchReferenceObjects({ ...target, filter: rule.source.filter || {} });
8063
+ } catch {
8064
+ rows = [];
8065
+ }
8066
+ this.mapColorRefs = Array.isArray(rows) ? rows : [];
8067
+ },
8068
+
7842
8069
  /**
7843
8070
  * Fetch what the board view needs: the rows of a reference column
7844
8071
  * field (once per field) and the narrowing select's options.
@@ -8835,6 +9062,10 @@ var script = {
8835
9062
  'filter-change': (event) => this.onFilterEvent(event),
8836
9063
  'clear-filters': () => this.onClearFilters(),
8837
9064
  'sort-change': (keys) => this.onSidebarSortChange(keys),
9065
+ 'saved-view': (event) => this.onSavedViewAction(event),
9066
+ 'tab-change': (tab) => {
9067
+ this.sidebarActiveTab = tab;
9068
+ },
8838
9069
  },
8839
9070
  };
8840
9071
  },
@@ -9479,6 +9710,13 @@ var script = {
9479
9710
  this.savedViewsLoading = true;
9480
9711
  try {
9481
9712
  this.savedViews = await useSavedViewsApi().fetchViews();
9713
+ // An empty answer is also what a misread response gives, so it
9714
+ // never empties the menu: entries are dropped only against a
9715
+ // list that came back with views in it.
9716
+ if (this.savedViews.length > 0) {
9717
+ this.syncSavedViewMenu();
9718
+ }
9719
+ this.applySavedViewFromRoute();
9482
9720
  } catch (error) {
9483
9721
  // eslint-disable-next-line no-console
9484
9722
  console.error('CnIndexPage: failed to fetch saved views', error);
@@ -9496,10 +9734,15 @@ var script = {
9496
9734
  * query implicitly resets `_page` to 1.
9497
9735
  *
9498
9736
  * @param {object} view The View API object to apply.
9737
+ * @param {{keepSavedViewKey?: boolean}} [options] Keep `?savedView=<id>` in the address, as a menu entry's link does.
9499
9738
  */
9500
- onApplySavedView(view) {
9739
+ onApplySavedView(view, { keepSavedViewKey = false } = {}) {
9501
9740
  this.appliedSavedViewId = view && (view.id || view.slug) ? String(view.id || view.slug) : '';
9502
9741
  const state = extractViewState(view);
9742
+ if (view && view.seeded === true) {
9743
+ const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
9744
+ state.filters = resolveFilterTokens(state.filters, ctx);
9745
+ }
9503
9746
  // A view that carries columns wins while it is applied; one without leaves the person's own layout.
9504
9747
  const viewColumns = view && view.query && Array.isArray(view.query.columns) ? view.query.columns.map(String) : [];
9505
9748
  this.appliedViewColumns = viewColumns.length > 0 ? viewColumns : null;
@@ -9518,14 +9761,18 @@ var script = {
9518
9761
  // Through `persistViewStateToRoute` rather than a raw replace,
9519
9762
  // because it is what records the keys this view wrote — without
9520
9763
  // that, a later "clear all" has nothing to delete them by.
9521
- this.persistViewStateToRoute(state);
9764
+ this.persistViewStateToRoute(state, { savedView: keepSavedViewKey ? this.appliedSavedViewId : '' });
9522
9765
  this.$emit('apply-view', view);
9523
9766
  return
9524
9767
  }
9525
9768
  if (!this.$router) {
9526
9769
  return
9527
9770
  }
9528
- const nav = this.$router.replace({ query: buildRouteQueryFromViewState(state) });
9771
+ const query = buildRouteQueryFromViewState(state);
9772
+ if (keepSavedViewKey) {
9773
+ query.savedView = this.appliedSavedViewId;
9774
+ }
9775
+ const nav = this.$router.replace({ query });
9529
9776
  // Swallow the duplicate-navigation rejection (Vue Router 3)
9530
9777
  // when the applied view matches the current query.
9531
9778
  if (nav && typeof nav.catch === 'function') {
@@ -9610,6 +9857,8 @@ var script = {
9610
9857
  }
9611
9858
  this.savedViews = [...this.savedViews, view];
9612
9859
  this.showSaveViewDialog = false;
9860
+ this.sidebarActiveTab = 'filters-tab';
9861
+ this.sidebarOpen = true;
9613
9862
  this.toastSavedView('success', translate('nextcloud-vue', 'View "{name}" saved', { name }));
9614
9863
  } catch (error) {
9615
9864
  // eslint-disable-next-line no-console
@@ -9659,6 +9908,125 @@ var script = {
9659
9908
  }
9660
9909
  },
9661
9910
 
9911
+ /**
9912
+ * One saved-view intent from the sidebar's Search tab.
9913
+ *
9914
+ * @param {{action: string, view: (object|null)}} event The intent.
9915
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-6
9916
+ */
9917
+ onSavedViewAction({ action, view } = {}) {
9918
+ const handlers = {
9919
+ apply: () => this.onApplySavedView(view),
9920
+ save: () => { this.showSaveViewDialog = true; },
9921
+ edit: () => this.onEditViewRequest(view),
9922
+ share: () => this.onShareViewRequest(view),
9923
+ presentation: () => this.onPresentationViewRequest(view),
9924
+ update: () => this.onUpdateViewRequest(view),
9925
+ copy: () => this.onCopyViewRequest(view),
9926
+ menu: () => this.onToggleSavedViewInMenu(view),
9927
+ delete: () => this.onDeleteViewRequest(view),
9928
+ };
9929
+ if (handlers[action]) {
9930
+ handlers[action]();
9931
+ }
9932
+ },
9933
+
9934
+ /**
9935
+ * Add a saved view to the app menu, or take it out. Stored per user
9936
+ * by CnAppRoot; the view itself is not touched, so a view shared
9937
+ * with this user can be added as well as an own one.
9938
+ *
9939
+ * @param {object} view The view.
9940
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
9941
+ */
9942
+ onToggleSavedViewInMenu(view) {
9943
+ const menu = this.cnSavedViewMenu;
9944
+ if (!view || !menu || typeof menu.toggle !== 'function' || this.savedViewPage === '') {
9945
+ return
9946
+ }
9947
+ menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name });
9948
+ },
9949
+
9950
+ /**
9951
+ * Bring this page's menu entries in line with the views it can show:
9952
+ * current names, and none for a view deleted or no longer shared.
9953
+ *
9954
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
9955
+ */
9956
+ syncSavedViewMenu() {
9957
+ const menu = this.cnSavedViewMenu;
9958
+ if (!menu || typeof menu.sync !== 'function' || this.savedViewPage === '') {
9959
+ return
9960
+ }
9961
+ menu.sync(this.savedViewPage, [...this.defaultFilterViews, ...this.visibleSavedViews]);
9962
+ },
9963
+
9964
+ /**
9965
+ * Apply the saved view the address names (`?savedView=<id>`, what a
9966
+ * menu entry links to), once the list holding it has loaded.
9967
+ *
9968
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
9969
+ */
9970
+ applySavedViewFromRoute() {
9971
+ const id = this.$route && this.$route.query ? this.$route.query.savedView : undefined;
9972
+ if (typeof id !== 'string' || id === '' || id === this.appliedSavedViewId) {
9973
+ return
9974
+ }
9975
+ const view = this.viewsForControl.find((v) => String(v.id) === id || String(v.slug) === id);
9976
+ if (view) {
9977
+ this.onApplySavedView(view, { keepSavedViewKey: true });
9978
+ }
9979
+ },
9980
+
9981
+ /**
9982
+ * Open the edit dialog for an own view (CnSavedViewsControl `@edit-request`).
9983
+ *
9984
+ * @param {object} view The View API object.
9985
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-3
9986
+ */
9987
+ onEditViewRequest(view) {
9988
+ this.viewPendingEdit = view;
9989
+ },
9990
+
9991
+ /**
9992
+ * Save a rename, and the current filters when asked
9993
+ * (CnSavedViewEditDialog `@confirm`). The PATCH carries `name`, plus
9994
+ * `query` only when the criteria are replaced, so nothing else on the
9995
+ * view is rewritten from this tab's copy. A refusal keeps the dialog open.
9996
+ *
9997
+ * @param {{ name: string, replaceCriteria: boolean }} payload The dialog payload.
9998
+ * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-3
9999
+ */
10000
+ async onEditViewConfirm({ name, replaceCriteria }) {
10001
+ const view = this.viewPendingEdit;
10002
+ if (!view) {
10003
+ return
10004
+ }
10005
+ const body = { name };
10006
+ if (replaceCriteria === true) {
10007
+ const state = this.isSelfFetchMode
10008
+ ? this.currentViewState()
10009
+ : extractViewStateFromRouteQuery((this.$route && this.$route.query) || {});
10010
+ body.query = buildViewCreatePayload({
10011
+ name,
10012
+ state,
10013
+ scope: savedViewScope(this.savedViewsPage),
10014
+ register: this.register,
10015
+ schema: this.schema,
10016
+ }).query;
10017
+ }
10018
+ try {
10019
+ const saved = await useSavedViewsApi().patchView(view.id, body);
10020
+ this.savedViews = this.savedViews.map((v) => (v.id === view.id ? { ...v, ...(saved || body) } : v));
10021
+ this.syncSavedViewMenu();
10022
+ this.viewPendingEdit = null;
10023
+ this.toastSavedView('success', translate('nextcloud-vue', 'View "{name}" saved', { name }));
10024
+ } catch (error) {
10025
+ const data = error?.response?.data;
10026
+ this.$refs.editViewDialog?.setError((data && (data.error || data.message)) || error?.message);
10027
+ }
10028
+ },
10029
+
9662
10030
  /**
9663
10031
  * Open the share dialog for an own view (CnSavedViewsControl `@share-request`).
9664
10032
  *
@@ -9777,6 +10145,7 @@ var script = {
9777
10145
  try {
9778
10146
  await useSavedViewsApi().deleteView(view.id);
9779
10147
  this.savedViews = this.savedViews.filter((v) => v.id !== view.id);
10148
+ this.syncSavedViewMenu();
9780
10149
  this.$refs.deleteViewConfirmDialog?.setResult({ success: true });
9781
10150
  } catch (error) {
9782
10151
  // eslint-disable-next-line no-console