@conduction/nextcloud-vue 2.62.0 → 2.64.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 (209) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +425 -39
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  19. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  25. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  26. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  27. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -0
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  35. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  36. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  37. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +253 -9
  39. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
  41. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  49. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  50. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  52. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  54. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  56. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
  58. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
  60. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  65. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  66. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +41 -2
  68. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  72. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  73. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  74. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  76. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  78. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  84. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +52 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
  88. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  89. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  90. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  94. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  95. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  96. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  97. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  98. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  99. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  100. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  110. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  111. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  112. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  113. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  114. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  115. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  116. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  117. package/dist/esm/composables/useDependencyCheck.js +65 -0
  118. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  119. package/dist/esm/composables/useWalkthrough.js +218 -6
  120. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  121. package/dist/esm/index.js +2 -0
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/l10n/en.json.js +45 -1
  124. package/dist/esm/l10n/en.json.js.map +1 -1
  125. package/dist/esm/l10n/nl.json.js +45 -1
  126. package/dist/esm/l10n/nl.json.js.map +1 -1
  127. package/dist/esm/nextcloud-vue.css +780 -81
  128. package/dist/esm/store/useObjectStore.js +8 -0
  129. package/dist/esm/store/useObjectStore.js.map +1 -1
  130. package/dist/esm/utils/columnFilters.js +311 -0
  131. package/dist/esm/utils/columnFilters.js.map +1 -0
  132. package/dist/esm/utils/groupAutocomplete.js +123 -0
  133. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  134. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  135. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  136. package/dist/esm/utils/pickerOptions.js +198 -0
  137. package/dist/esm/utils/pickerOptions.js.map +1 -0
  138. package/dist/esm/utils/schema.js +83 -7
  139. package/dist/esm/utils/schema.js.map +1 -1
  140. package/dist/esm/utils/schemaRefSlug.js +19 -3
  141. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  142. package/dist/esm/utils/timelineEvents.js +174 -0
  143. package/dist/esm/utils/timelineEvents.js.map +1 -0
  144. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  145. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  146. package/dist/esm/utils/walkthroughSignals.js +88 -0
  147. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  148. package/dist/nextcloud-vue.cjs.js +13088 -7937
  149. package/dist/nextcloud-vue.cjs.js.map +2 -2
  150. package/dist/nextcloud-vue.css +780 -81
  151. package/l10n/en.json +45 -0
  152. package/l10n/nl.json +45 -0
  153. package/package.json +1 -1
  154. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  155. package/src/components/CnAdminActionCard/index.js +4 -0
  156. package/src/components/CnAppNav/CnAppNav.vue +568 -43
  157. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  158. package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
  159. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  160. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  161. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  162. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  163. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
  164. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  165. package/src/components/CnDataTable/CnDataTable.md +3 -0
  166. package/src/components/CnDataTable/CnDataTable.vue +305 -5
  167. package/src/components/CnDetailPage/CnDetailPage.md +4 -0
  168. package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
  169. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  170. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  171. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  172. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +116 -6
  173. package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
  174. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  175. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
  176. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  177. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  178. package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
  179. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  180. package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
  181. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  182. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  183. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  184. package/src/components/CnTimelineWidget/index.js +4 -0
  185. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  186. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  187. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  188. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  189. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  190. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  191. package/src/components/index.js +2 -0
  192. package/src/composables/useDependencyCheck.js +61 -0
  193. package/src/composables/useWalkthrough.js +217 -5
  194. package/src/css/dashboard.css +49 -0
  195. package/src/css/detail-page.css +29 -4
  196. package/src/css/kpi-card.css +24 -1
  197. package/src/css/table.css +99 -6
  198. package/src/index.js +2 -0
  199. package/src/schemas/app-manifest-v2.schema.json +135 -3
  200. package/src/store/useObjectStore.js +8 -0
  201. package/src/utils/columnFilters.js +324 -0
  202. package/src/utils/groupAutocomplete.js +119 -0
  203. package/src/utils/libraryWidgetKeys.js +2 -0
  204. package/src/utils/pickerOptions.js +194 -0
  205. package/src/utils/schema.js +83 -7
  206. package/src/utils/schemaRefSlug.js +19 -3
  207. package/src/utils/timelineEvents.js +171 -0
  208. package/src/utils/validateManifestV2.compiled.js +2 -2
  209. package/src/utils/walkthroughSignals.js +94 -0
@@ -203,3 +203,7 @@ Static-manifest apps use `useAppManifest('myapp', bundledManifest)`, which fetch
203
203
  | `softDependencyNotices` | Boolean | `false` | **Deprecated.** Restores the in-shell orange notices, one per unresolved optional dependency. Off by default: they stacked one per optional leaf and pushed the app's own content below the fold, for an audience who cannot act on them. Mount `CnLeafDependencySettings` in admin settings instead. Slated for removal. |
204
204
 
205
205
  `unresolvedSoftDependencies` is unchanged and still exposed, so an app rendering its own surface from it keeps working.
206
+
207
+ ### Without a navigation (`hideMenu`)
208
+
209
+ `hideMenu` (default `false`) renders neither the default CnAppNav nor the `#menu` slot, so the content starts at the left edge.
@@ -199,7 +199,13 @@
199
199
  <!-- @binding {boolean} isOwner Whether the user owns this app (ADR-079 §3). -->
200
200
  <!-- @binding {boolean} isAdmin Whether the user administers the instance; gates the Admin-settings link. -->
201
201
  <!-- @binding {string} appId The app id used to build the Admin-settings target. -->
202
- <slot name="menu"
202
+ <!-- `hideMenu` renders no navigation at all, so the content starts
203
+ at the left edge. An EMPTY #menu override cannot do that: an
204
+ empty slot falls back to the default CnAppNav (ensureValidVNode),
205
+ which is why launchpad passed a hidden empty span. -->
206
+ <slot
207
+ v-if="!hideMenu"
208
+ name="menu"
203
209
  :manifest="menuManifest"
204
210
  :permissions="permissions"
205
211
  :isOwner="isOwner"
@@ -509,7 +515,7 @@
509
515
  :resume="walkthroughResume"
510
516
  :translate="translate"
511
517
  @complete="onWalkthroughComplete"
512
- @dismiss="onWalkthroughComplete" />
518
+ @progress="onWalkthroughProgress" />
513
519
  </slot>
514
520
  <!--
515
521
  User-settings modal. Always mounted so descendants can
@@ -554,12 +560,27 @@
554
560
  <p class="cn-app-root__walkthrough-hint">
555
561
  {{ restartWalkthroughHint }}
556
562
  </p>
557
- <NcButton variant="secondary" @click="restartWalkthroughFromSettings">
558
- <template #icon>
559
- <Restart :size="20" />
560
- </template>
561
- {{ restartWalkthroughLabel }}
562
- </NcButton>
563
+ <div class="cn-app-root__walkthrough-actions">
564
+ <NcButton
565
+ v-if="walkthroughProgressValue"
566
+ variant="primary"
567
+ data-testid="cn-walkthrough-continue"
568
+ @click="continueWalkthroughFromSettings">
569
+ <template #icon>
570
+ <Play :size="20" />
571
+ </template>
572
+ {{ continueWalkthroughLabel }}
573
+ </NcButton>
574
+ <NcButton
575
+ variant="secondary"
576
+ data-testid="cn-walkthrough-restart"
577
+ @click="restartWalkthroughFromSettings">
578
+ <template #icon>
579
+ <Restart :size="20" />
580
+ </template>
581
+ {{ walkthroughProgressValue ? startOverWalkthroughLabel : restartWalkthroughLabel }}
582
+ </NcButton>
583
+ </div>
563
584
  </NcAppSettingsSection>
564
585
  </slot>
565
586
  <!--
@@ -638,11 +659,13 @@
638
659
  import { getCurrentUser } from '@nextcloud/auth'
639
660
  import axios from '@nextcloud/axios'
640
661
  import { loadState } from '@nextcloud/initial-state'
662
+ import { translate as t } from '@nextcloud/l10n'
641
663
  import { generateUrl } from '@nextcloud/router'
642
664
  import { NcAppContent, NcAppSettingsDialog, NcAppSettingsSection, NcButton, NcContent, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
643
665
  import { computed, reactive, shallowRef, watch } from 'vue'
644
666
  import DatabaseSearchOutline from 'vue-material-design-icons/DatabaseSearchOutline.vue'
645
667
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
668
+ import Play from 'vue-material-design-icons/Play.vue'
646
669
  import Restart from 'vue-material-design-icons/Restart.vue'
647
670
  import CnAiCompanion from '../CnAiCompanion/CnAiCompanion.vue'
648
671
  import CnAppLoading from '../CnAppLoading/CnAppLoading.vue'
@@ -667,9 +690,12 @@ import { useSupportDialog } from '../../composables/useSupportDialog.js'
667
690
  import { provideTenantContext } from '../../composables/useTenantContext.js'
668
691
  import { useUserPreferences } from '../../composables/useUserPreferences.js'
669
692
  import {
693
+ loadWalkthroughProgress,
670
694
  loadWalkthroughSeenVersion,
671
695
  normaliseSeenVersion,
696
+ persistWalkthroughProgress,
672
697
  persistWalkthroughSeenVersion,
698
+ readLocalWalkthroughProgress,
673
699
  readLocalWalkthroughSeenVersion,
674
700
  useWalkthrough,
675
701
  } from '../../composables/useWalkthrough.js'
@@ -680,6 +706,7 @@ import { BUILT_IN_FORMATTERS } from '../../utils/builtInFormatters.js'
680
706
  import { DEFAULT_FORGE, resolveForge } from '../../utils/forge.js'
681
707
  import { BUILT_IN_KB_PROVIDERS } from '../../utils/kbSearchProviders.js'
682
708
  import { installModalStack, uninstallModalStack } from '../../utils/modalStack.js'
709
+ import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
683
710
  import { passesContextPredicates } from '../../utils/visibleIfContext.js'
684
711
 
685
712
  /**
@@ -749,6 +776,7 @@ export default {
749
776
  NcNoteCard,
750
777
  DatabaseSearchOutline,
751
778
  OpenInNew,
779
+ Play,
752
780
  Restart,
753
781
  CnAppNav,
754
782
  CnAppLoading,
@@ -1028,6 +1056,13 @@ export default {
1028
1056
  * the counts.
1029
1057
  */
1030
1058
  cnMenuCounts: this.cnMenuCounts,
1059
+ /**
1060
+ * Reactive `{ [menuItemId]: number }` totals for menu entries
1061
+ * whose `count` is an object (`{ register, schema, filter? }`),
1062
+ * populated by `_hydrateMenuItemCounts()`. Read by
1063
+ * `CnAppNav.resolveCount()`.
1064
+ */
1065
+ cnMenuItemCounts: this.cnMenuItemCounts,
1031
1066
  }
1032
1067
  },
1033
1068
 
@@ -1171,6 +1206,19 @@ export default {
1171
1206
  required: true,
1172
1207
  },
1173
1208
 
1209
+ /**
1210
+ * Render no app navigation at all: neither the default CnAppNav nor
1211
+ * the `#menu` slot, so the content starts at the left edge (a start
1212
+ * page without a menu). Off by default; the navigation renders as it
1213
+ * always has.
1214
+ *
1215
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-an-app-root-can-hide-its-menu
1216
+ */
1217
+ hideMenu: {
1218
+ type: Boolean,
1219
+ default: false,
1220
+ },
1221
+
1174
1222
  /**
1175
1223
  * Human-readable name shown in the Nextcloud top bar. When set it
1176
1224
  * overrides the technical `appId` so a virtual app shows its own name
@@ -1686,6 +1734,13 @@ export default {
1686
1734
  * picked up by CnAppNav's reactive render.
1687
1735
  */
1688
1736
  cnMenuCounts: reactive({}),
1737
+ /**
1738
+ * Reactive `{ [menuItemId]: number }` map of filtered totals, one
1739
+ * per menu entry whose `count` is an object. Provided to
1740
+ * descendants as `cnMenuItemCounts`. Populated by
1741
+ * `_hydrateMenuItemCounts()` at mount.
1742
+ */
1743
+ cnMenuItemCounts: reactive({}),
1689
1744
  /**
1690
1745
  * Open state of the host NcAppSettingsDialog. Toggled
1691
1746
  * to `true` by the provided `cnOpenUserSettings()`
@@ -1776,6 +1831,15 @@ export default {
1776
1831
  * @type {string}
1777
1832
  */
1778
1833
  walkthroughSeenVersionValue: readLocalWalkthroughSeenVersion(this.appId),
1834
+ /**
1835
+ * Where the user is in an unfinished tour (`{ tourId, stepId,
1836
+ * index, version }`), or null. Seeded from the local mirror, then
1837
+ * replaced by the per-user `<completionConfigKey>-progress`
1838
+ * preference. The tour resumes at this step on the next visit.
1839
+ *
1840
+ * @type {object|null}
1841
+ */
1842
+ walkthroughProgressValue: readLocalWalkthroughProgress(this.appId),
1779
1843
  /**
1780
1844
  * Whether `walkthroughSeenVersionValue` is settled. Starts `true`
1781
1845
  * when the manifest declares no `completionConfigKey` (the local
@@ -2529,13 +2593,15 @@ export default {
2529
2593
  try {
2530
2594
  const p = new URLSearchParams(window.location.search)
2531
2595
  const tourId = p.get('cn_resume_tour')
2532
- if (!tourId) {
2533
- return null
2596
+ if (tourId) {
2597
+ return { tourId, stepId: p.get('cn_resume_step') || '' }
2534
2598
  }
2535
- return { tourId, stepId: p.get('cn_resume_step') || '' }
2536
2599
  } catch {
2537
- return null
2600
+ // No URL token; fall through to the remembered progress.
2538
2601
  }
2602
+ // An unfinished tour continues at the step the user reached.
2603
+ const progress = this.walkthroughProgressValue
2604
+ return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
2539
2605
  },
2540
2606
 
2541
2607
  phase() {
@@ -2778,6 +2844,24 @@ export default {
2778
2844
  return this.translate('Restart walkthrough')
2779
2845
  },
2780
2846
 
2847
+ /**
2848
+ * Label for continuing an unfinished tour at the remembered step.
2849
+ *
2850
+ * @return {string}
2851
+ */
2852
+ continueWalkthroughLabel() {
2853
+ return t('nextcloud-vue', 'Continue where you left off')
2854
+ },
2855
+
2856
+ /**
2857
+ * Label for restarting an unfinished tour from the first step.
2858
+ *
2859
+ * @return {string}
2860
+ */
2861
+ startOverWalkthroughLabel() {
2862
+ return t('nextcloud-vue', 'Start over')
2863
+ },
2864
+
2781
2865
  /**
2782
2866
  * Resolve the active modal's Vue component from the registry.
2783
2867
  * Returns null when no modal is open or the key no longer resolves.
@@ -2875,6 +2959,7 @@ export default {
2875
2959
  this._validateRegistry()
2876
2960
  this._warnCustomComponentsDeprecation()
2877
2961
  this._hydrateMenuCounts()
2962
+ this._hydrateMenuItemCounts()
2878
2963
  return
2879
2964
  }
2880
2965
 
@@ -2903,6 +2988,7 @@ export default {
2903
2988
  this._validateRegistry()
2904
2989
  this._warnCustomComponentsDeprecation()
2905
2990
  this._hydrateMenuCounts()
2991
+ this._hydrateMenuItemCounts()
2906
2992
  },
2907
2993
 
2908
2994
  beforeUnmount() {
@@ -3281,10 +3367,12 @@ export default {
3281
3367
  return
3282
3368
  }
3283
3369
  try {
3284
- this.walkthroughSeenVersionValue = await loadWalkthroughSeenVersion(
3285
- this.appId,
3286
- this.walkthroughConfigKey,
3287
- )
3370
+ const [seen, progress] = await Promise.all([
3371
+ loadWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey),
3372
+ loadWalkthroughProgress(this.appId, this.walkthroughConfigKey),
3373
+ ])
3374
+ this.walkthroughSeenVersionValue = seen
3375
+ this.walkthroughProgressValue = progress
3288
3376
  } finally {
3289
3377
  this.walkthroughSeenResolved = true
3290
3378
  }
@@ -3308,12 +3396,68 @@ export default {
3308
3396
  // Fire-and-forget: the loader never rejects and the local mirror is
3309
3397
  // already written, so a failed PUT must not surface as an error.
3310
3398
  persistWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey, v)
3399
+ // A finished or skipped tour has nothing to continue.
3400
+ if (this.walkthroughProgressValue) {
3401
+ this.walkthroughProgressValue = null
3402
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, null)
3403
+ }
3311
3404
  /**
3312
3405
  * @event walkthrough-complete Emitted when the walkthrough finishes or is dismissed.
3313
3406
  */
3314
3407
  this.$emit('walkthrough-complete')
3315
3408
  },
3316
3409
 
3410
+ /**
3411
+ * Remember where the user is in the tour, so a reload or a restart
3412
+ * entry continues there (local mirror + per-user preference).
3413
+ *
3414
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
3415
+ * @param {object} progress `{ tourId, stepId, index }` from CnWalkthrough.
3416
+ * @return {void}
3417
+ */
3418
+ onWalkthroughProgress(progress) {
3419
+ if (!progress || !progress.tourId) {
3420
+ return
3421
+ }
3422
+ // The first step of a fresh tour is where a restart lands anyway,
3423
+ // so it costs no request.
3424
+ if (progress.index === 0 && !this.walkthroughProgressValue) {
3425
+ return
3426
+ }
3427
+ const value = {
3428
+ tourId: progress.tourId,
3429
+ stepId: progress.stepId || '',
3430
+ index: progress.index || 0,
3431
+ version: String((this.manifest && this.manifest.version) || ''),
3432
+ }
3433
+ this.walkthroughProgressValue = value
3434
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3435
+ },
3436
+
3437
+ /**
3438
+ * Continue an unfinished tour from the user-settings dialog: show a
3439
+ * paused tour again, or start the remembered tour at its step.
3440
+ *
3441
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
3442
+ * @return {void}
3443
+ */
3444
+ continueWalkthroughFromSettings() {
3445
+ this.userSettingsOpen = false
3446
+ if (!this.walkthroughEnabled) {
3447
+ return
3448
+ }
3449
+ setTimeout(() => {
3450
+ const wt = useWalkthrough(this.appId, this.manifest)
3451
+ if (wt.resumePaused()) {
3452
+ return
3453
+ }
3454
+ const progress = this.walkthroughProgressValue
3455
+ if (progress) {
3456
+ wt.resumeAt(progress.tourId, progress.stepId)
3457
+ }
3458
+ }, 50)
3459
+ },
3460
+
3317
3461
  /**
3318
3462
  * Replay the product tour from the user-settings dialog. Closes the
3319
3463
  * dialog first, then restarts the first declared tour on the next tick
@@ -3501,6 +3645,63 @@ export default {
3501
3645
  })
3502
3646
  },
3503
3647
 
3648
+ /**
3649
+ * Fetch the total of every menu entry (top level and children) whose
3650
+ * `count` is an object `{ register, schema, filter? }`: one
3651
+ * `GET /api/objects/{register}/{schema}?_limit=1` per entry with the
3652
+ * filter's tokens (`@me`, `@today`, ...) resolved at fetch time, the
3653
+ * response `total` written to `cnMenuItemCounts[item.id]`. The
3654
+ * request goes straight through axios rather than the object store,
3655
+ * so the index page's whole-schema total in the store is left alone.
3656
+ * Failures leave the badge unrendered.
3657
+ *
3658
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-menu-entry-counts-a-filtered-list
3659
+ * @return {void}
3660
+ * @private
3661
+ */
3662
+ _hydrateMenuItemCounts() {
3663
+ const collect = (items) => {
3664
+ const out = []
3665
+ for (const item of items ?? []) {
3666
+ const count = item?.count
3667
+ if (count && typeof count === 'object' && typeof item.id === 'string' && count.register && count.schema) {
3668
+ out.push(item)
3669
+ }
3670
+ if (Array.isArray(item?.children)) {
3671
+ out.push(...collect(item.children))
3672
+ }
3673
+ }
3674
+ return out
3675
+ }
3676
+ for (const item of collect(this.manifest?.menu)) {
3677
+ this._fetchMenuItemCount(item)
3678
+ }
3679
+ },
3680
+
3681
+ /**
3682
+ * One filtered-count request for a menu entry; see
3683
+ * `_hydrateMenuItemCounts`.
3684
+ *
3685
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-menu-entry-counts-a-filtered-list
3686
+ * @param {{ id: string, count: { register: string, schema: string, filter?: object } }} item The menu entry.
3687
+ * @return {Promise<void>}
3688
+ * @private
3689
+ */
3690
+ async _fetchMenuItemCount(item) {
3691
+ try {
3692
+ const { register, schema, filter } = item.count
3693
+ const resolved = resolveFilterTokens(filter && typeof filter === 'object' ? filter : {}, {})
3694
+ const url = generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}`)
3695
+ const { data } = await axios.get(url, { params: { _limit: 1, ...resolved } })
3696
+ const total = data?.total
3697
+ if (typeof total === 'number' && total >= 0) {
3698
+ this.cnMenuItemCounts[item.id] = total
3699
+ }
3700
+ } catch {
3701
+ // Non-fatal: the badge stays unrendered.
3702
+ }
3703
+ },
3704
+
3504
3705
  /**
3505
3706
  * Hydrate all menu counts with a single `POST /api/objects/counts`
3506
3707
  * (OpenRegister batched-counts endpoint). Distributes each returned
@@ -3646,6 +3847,12 @@ export default {
3646
3847
  color: var(--color-text-maxcontrast);
3647
3848
  }
3648
3849
 
3850
+ .cn-app-root__walkthrough-actions {
3851
+ display: flex;
3852
+ flex-wrap: wrap;
3853
+ gap: 8px;
3854
+ }
3855
+
3649
3856
  .cn-app-root__integrations-hint {
3650
3857
  margin-bottom: 12px;
3651
3858
  color: var(--color-text-maxcontrast);
@@ -69,12 +69,27 @@ export default {
69
69
  components: { CnDetailCard, NcLoadingIcon },
70
70
 
71
71
  props: {
72
- /** OpenRegister register id (slug or uuid). */
73
- register: { type: String, required: true },
74
- /** OpenRegister schema id (slug or uuid). */
75
- schema: { type: String, required: true },
76
- /** Parent object id. */
77
- objectId: { type: String, required: true },
72
+ /** OpenRegister register id (slug or uuid). Optional for `scope: "app"`. */
73
+ register: { type: String, default: '' },
74
+ /** OpenRegister schema id (slug or uuid). Optional for `scope: "app"`. */
75
+ schema: { type: String, default: '' },
76
+ /** Parent object id. Required for `scope: "object"` (the default); unused for `scope: "app"`. */
77
+ objectId: { type: String, default: '' },
78
+ /**
79
+ * What the card lists. `object` (the default) is one object's trail
80
+ * and needs `register`, `schema` and `objectId`. `app` is the
81
+ * app-wide feed: the entries of every object the caller may read
82
+ * (OpenRegister's `/audit-trails/readable`), narrowed to `register`
83
+ * and `schema` when those are set.
84
+ *
85
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed
86
+ */
87
+ scope: {
88
+ type: String,
89
+ default: 'object',
90
+ validator: (value) => ['object', 'app'].includes(value),
91
+ },
92
+
78
93
  /** Rendering surface — passed for AD-19 surface fallback consumers. */
79
94
  surface: {
80
95
  type: String,
@@ -116,13 +131,38 @@ export default {
116
131
  displayedEntries() {
117
132
  return this.entries.slice(0, this.maxDisplay)
118
133
  },
134
+
135
+ /**
136
+ * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the
137
+ * object's own trail, or the app-wide readable feed narrowed to the
138
+ * register and schema when set.
139
+ *
140
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed
141
+ * @return {string}
142
+ */
143
+ fetchUrl() {
144
+ const params = new URLSearchParams({ limit: String(this.maxDisplay) })
145
+ if (this.scope === 'app') {
146
+ if (this.register) {
147
+ params.set('register', this.register)
148
+ }
149
+ if (this.schema) {
150
+ params.set('schema', this.schema)
151
+ }
152
+ return `${this.apiBase}/audit-trails/readable?${params.toString()}`
153
+ }
154
+ if (!this.register || !this.schema || !this.objectId) {
155
+ return ''
156
+ }
157
+ return `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trail?${params.toString()}`
158
+ },
119
159
  },
120
160
 
121
161
  watch: {
122
- objectId: {
162
+ fetchUrl: {
123
163
  immediate: true,
124
- handler(id) {
125
- if (id) {
164
+ handler(url) {
165
+ if (url) {
126
166
  this.fetchEntries()
127
167
  }
128
168
  },
@@ -130,20 +170,25 @@ export default {
130
170
  },
131
171
 
132
172
  methods: {
173
+ /**
174
+ * Read the entries from `fetchUrl`. The readable feed answers
175
+ * `{ rows, nextCursor }`, the object trail `{ results }`.
176
+ *
177
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed
178
+ * @return {Promise<void>}
179
+ */
133
180
  async fetchEntries() {
134
- if (!this.register || !this.schema || !this.objectId) {
181
+ const url = this.fetchUrl
182
+ if (!url) {
135
183
  return
136
184
  }
137
185
  this.loading = true
138
186
  try {
139
- const params = new URLSearchParams({ limit: String(this.maxDisplay) })
140
- const response = await fetch(
141
- prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trail?${params.toString()}`),
142
- { headers: buildHeaders() },
143
- )
187
+ const response = await fetch(prefixUrl(url), { headers: buildHeaders() })
144
188
  if (response.ok) {
145
189
  const data = await response.json()
146
- this.entries = data.results || data || []
190
+ const list = data.results || data.rows || data
191
+ this.entries = Array.isArray(list) ? list : []
147
192
  } else {
148
193
  this.entries = []
149
194
  }
@@ -5,10 +5,11 @@
5
5
 
6
6
  <template>
7
7
  <CnAuditTrailCard
8
- v-if="resolvedRegister && resolvedSchema && resolvedObjectId"
8
+ v-if="resolvedScope === 'app' || (resolvedRegister && resolvedSchema && resolvedObjectId)"
9
9
  :register="resolvedRegister"
10
10
  :schema="resolvedSchema"
11
11
  :objectId="resolvedObjectId"
12
+ :scope="resolvedScope"
12
13
  :title="resolvedTitle"
13
14
  :maxDisplay="resolvedMaxDisplay" />
14
15
  </template>
@@ -70,6 +71,15 @@ export default {
70
71
  title: { type: String, default: '' },
71
72
  /** Maximum audit rows to render (0 falls back to the card default). */
72
73
  maxDisplay: { type: Number, default: 0 },
74
+ /**
75
+ * `app` lists the app-wide feed (every object the caller may read,
76
+ * narrowed to `register` / `schema` when set) and needs no object;
77
+ * `object` (the default) is one object's trail. Also read from
78
+ * `content.scope`.
79
+ *
80
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed
81
+ */
82
+ scope: { type: String, default: '' },
73
83
  /** Stored widget content blob (CnDashboardPage registry branch): `{ title, maxDisplay, register?, schema?, objectId? }`. */
74
84
  content: { type: Object, default: () => ({}) },
75
85
  },
@@ -115,6 +125,17 @@ export default {
115
125
  return typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''
116
126
  },
117
127
 
128
+ /**
129
+ * The feed scope: explicit prop → content → `object`.
130
+ *
131
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed
132
+ * @return {('object'|'app')}
133
+ */
134
+ resolvedScope() {
135
+ const s = this.scope || this.content.scope
136
+ return s === 'app' ? 'app' : 'object'
137
+ },
138
+
118
139
  /**
119
140
  * The card title: explicit prop → content → '' (card default).
120
141
  *
@@ -177,6 +177,9 @@ import { safeHref } from '../../utils/safeHref.js'
177
177
  import { formatValue } from '../../utils/schema.js'
178
178
  import { CnStatusBadge } from '../CnStatusBadge/index.js'
179
179
 
180
+ /** A uuid in any version, the shape OpenRegister ids take. */
181
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
182
+
180
183
  /**
181
184
  * Module-level set of column keys already warned about for a
182
185
  * `widget:"link"` declaration with no resolvable target — guarantees
@@ -483,13 +486,20 @@ export default {
483
486
  *
484
487
  * @return {string}
485
488
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells
489
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
486
490
  */
487
491
  dateLabel() {
488
492
  const date = parseDateValue(this.value)
489
493
  if (date === null) {
490
494
  return ''
491
495
  }
492
- const options = { day: 'numeric', month: 'short', year: 'numeric' }
496
+ // `widgetProps.showTime: true` adds the time of day ("5 Oct 2026,
497
+ // 08:30"); `widgetProps.timeOnly: true` shows just the time
498
+ // ("08:30"), for a timetable row. Default: the date alone.
499
+ const props = this.widgetProps || {}
500
+ const options = props.timeOnly === true
501
+ ? { hour: '2-digit', minute: '2-digit' }
502
+ : { day: 'numeric', month: 'short', year: 'numeric', ...(props.showTime === true ? { hour: '2-digit', minute: '2-digit' } : {}) }
493
503
  let locale
494
504
  try {
495
505
  locale = getCanonicalLocale()
@@ -738,6 +748,27 @@ export default {
738
748
  return undefined
739
749
  },
740
750
 
751
+ /**
752
+ * Whether the cell draws a uuid, and so takes the monospace uuid
753
+ * style. The property's `format: "uuid"` alone used to decide this,
754
+ * so a reference that a formatter or a widget had resolved to its
755
+ * name was still drawn as code. Now the value has to be a uuid AND
756
+ * nothing resolves it: a resolving widget or formatter, or a value
757
+ * that is not a uuid, renders in the normal text style.
758
+ *
759
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-resolved-reference-is-not-drawn-as-a-uuid
760
+ * @return {boolean}
761
+ */
762
+ showsUuid() {
763
+ if (this.property?.format !== 'uuid') {
764
+ return false
765
+ }
766
+ if (this.widgetComponent || this.widget || this.hasFormatter) {
767
+ return false
768
+ }
769
+ return typeof this.value === 'string' && UUID_PATTERN.test(this.value)
770
+ },
771
+
741
772
  cellClass() {
742
773
  const classes = []
743
774
  if (this.propertyType === 'boolean') {
@@ -749,7 +780,7 @@ export default {
749
780
  if (this.property?.format === 'date-time' || this.property?.format === 'date') {
750
781
  classes.push('cn-cell-renderer--date')
751
782
  }
752
- if (this.property?.format === 'uuid') {
783
+ if (this.showsUuid) {
753
784
  classes.push('cn-cell-renderer--uuid')
754
785
  }
755
786
  if (this.propertyType === 'integer' || this.propertyType === 'number') {