@conduction/nextcloud-vue 2.70.0 → 2.71.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 (50) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  5. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  6. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +38 -42
  7. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  8. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
  9. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  10. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
  11. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  12. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +22 -0
  13. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +1 -1
  15. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
  17. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  18. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  19. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  20. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
  21. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  22. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
  23. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  24. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  25. package/dist/esm/composables/useWalkthrough.js +11 -3
  26. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  27. package/dist/esm/nextcloud-vue.css +53 -0
  28. package/dist/esm/utils/columnFilters.js +54 -4
  29. package/dist/esm/utils/columnFilters.js.map +1 -1
  30. package/dist/esm/utils/userAutocomplete.js +69 -3
  31. package/dist/esm/utils/userAutocomplete.js.map +1 -1
  32. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  33. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  34. package/dist/nextcloud-vue.cjs.js +615 -235
  35. package/dist/nextcloud-vue.cjs.js.map +2 -2
  36. package/dist/nextcloud-vue.css +53 -0
  37. package/package.json +1 -1
  38. package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
  39. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -18
  40. package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
  41. package/src/components/CnIndexPage/CnIndexPage.vue +23 -1
  42. package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
  43. package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
  44. package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
  45. package/src/composables/useWalkthrough.js +11 -3
  46. package/src/css/dashboard.css +44 -0
  47. package/src/schemas/app-manifest-v2.schema.json +2 -1
  48. package/src/utils/columnFilters.js +54 -4
  49. package/src/utils/userAutocomplete.js +68 -2
  50. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 35617052 bytes
4
- sha256: 8d17da3a5f3f43dba63932341520f63af085b4d61c1b614631488abb6ecd41d8
3
+ size: 35657637 bytes
4
+ sha256: b7b966a528988db88e213ce0afc88832aae9c6b60341877ba5226dc3d833f211
@@ -2841,6 +2841,50 @@ a.cn-kpi-card--stacked:focus-visible {
2841
2841
  .cn-requires-app.cn-requires-app .empty-content__action {
2842
2842
  margin-top: var(--default-grid-baseline, 4px);
2843
2843
  }
2844
+
2845
+ /* NcEmptyContent forces its icon's svg to 64px with !important while the
2846
+ * rule above lets the icon box shrink, so the svg spilled out of its box and
2847
+ * over the name. Hold the svg to the size the caller asked for. */
2848
+ .cn-requires-app.cn-requires-app .empty-content__icon svg {
2849
+ width: 32px !important;
2850
+ height: 32px !important;
2851
+ max-width: 32px !important;
2852
+ max-height: 32px !important;
2853
+ }
2854
+
2855
+ /* The dashboard's compact "Install <app>" placeholder: two lines of text and
2856
+ * the button, nothing else, so it fits a two-row tile even under a title row.
2857
+ * `safe center` keeps the top in view when a tile is smaller still, and the
2858
+ * body scrolls rather than clipping the button. */
2859
+ .cn-requires-app--compact {
2860
+ display: flex;
2861
+ flex-direction: column;
2862
+ align-items: center;
2863
+ justify-content: safe center;
2864
+ gap: calc(2 * var(--default-grid-baseline, 4px));
2865
+ box-sizing: border-box;
2866
+ height: 100%;
2867
+ min-height: 0;
2868
+ overflow: auto;
2869
+ padding: calc(2 * var(--default-grid-baseline, 4px));
2870
+ text-align: center;
2871
+ }
2872
+
2873
+ .cn-requires-app--compact .cn-requires-app__description {
2874
+ display: -webkit-box;
2875
+ margin: 0;
2876
+ overflow: hidden;
2877
+ color: var(--color-text-maxcontrast);
2878
+ font-size: 0.9em;
2879
+ line-height: 1.4;
2880
+ -webkit-box-orient: vertical;
2881
+ -webkit-line-clamp: 2;
2882
+ line-clamp: 2;
2883
+ }
2884
+
2885
+ .cn-requires-app--compact .button-vue {
2886
+ flex-shrink: 0;
2887
+ }
2844
2888
  /* ========================================
2845
2889
  @conduction/nextcloud-vue — CnDetailPage Styles
2846
2890
  ======================================== */
@@ -15645,6 +15689,15 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
15645
15689
  }
15646
15690
 
15647
15691
 
15692
+ /* A select option breaks between words only, never inside one. */
15693
+ .cn-form-dialog__option[data-v-c3eeb0a2] {
15694
+ display: block;
15695
+ min-width: 0;
15696
+ white-space: normal;
15697
+ overflow-wrap: normal;
15698
+ word-break: normal;
15699
+ hyphens: manual;
15700
+ }
15648
15701
  .cn-form-dialog__draft-actions[data-v-c3eeb0a2] {
15649
15702
  display: flex;
15650
15703
  gap: 8px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.70.0",
3
+ "version": "2.71.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -513,8 +513,10 @@
513
513
  :manifest="manifest"
514
514
  :seenVersion="walkthroughSeenVersion"
515
515
  :resume="walkthroughResume"
516
+ :autoStart="!walkthroughPaused"
516
517
  :translate="translate"
517
518
  @complete="onWalkthroughComplete"
519
+ @pause="onWalkthroughPause"
518
520
  @progress="onWalkthroughProgress" />
519
521
  </slot>
520
522
  <!--
@@ -934,8 +936,11 @@ export default {
934
936
  * Restart entry for the product walkthrough (ADR-043). Descendants
935
937
  * (a menu/settings "Replay walkthrough" entry, or a manifest menu
936
938
  * `action: "replay-walkthrough"`) call this to re-run a tour. With no
937
- * `tourId` the first declared tour is used.
939
+ * `tourId` the first declared tour is used. An unfinished tour
940
+ * continues where the user was (a paused tour, else the saved
941
+ * step); "Start over" in the user settings goes back to step 1.
938
942
  *
943
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
939
944
  * @param {string} [tourId] The tour to restart.
940
945
  * @return {void}
941
946
  */
@@ -945,9 +950,18 @@ export default {
945
950
  }
946
951
  const wt = useWalkthrough(this.appId, this.manifest)
947
952
  const id = tourId || (this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id)
948
- if (id) {
949
- wt.restart(id)
953
+ if (!id) {
954
+ return
955
+ }
956
+ const progress = this.walkthroughProgressValue
957
+ this.unpauseWalkthroughProgress()
958
+ if (wt.paused.value && wt.activeTour.value && wt.activeTour.value.id === id && wt.resumePaused()) {
959
+ return
960
+ }
961
+ if (progress && progress.tourId === id && wt.resumeAt(id, progress.stepId)) {
962
+ return
950
963
  }
964
+ wt.restart(id)
951
965
  },
952
966
 
953
967
  /**
@@ -1528,7 +1542,7 @@ export default {
1528
1542
  },
1529
1543
  },
1530
1544
 
1531
- emits: ['setup-complete', 'walkthrough-complete'],
1545
+ emits: ['setup-complete', 'setup-wizard-dismissed', 'walkthrough-complete'],
1532
1546
 
1533
1547
  /**
1534
1548
  * Component-instance state for the capabilities guard.
@@ -2599,9 +2613,21 @@ export default {
2599
2613
  } catch {
2600
2614
  // No URL token; fall through to the remembered progress.
2601
2615
  }
2602
- // An unfinished tour continues at the step the user reached.
2616
+ // An unfinished tour continues at the step the user reached,
2617
+ // unless the user paused it: then it waits for "Continue".
2603
2618
  const progress = this.walkthroughProgressValue
2604
- return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
2619
+ return progress && !progress.paused ? { tourId: progress.tourId, stepId: progress.stepId } : null
2620
+ },
2621
+
2622
+ /**
2623
+ * Whether the user paused the tour (X, ESC or the dim). A paused tour
2624
+ * stays hidden across page loads until the user picks "Continue".
2625
+ *
2626
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
2627
+ * @return {boolean} True while the saved progress is paused.
2628
+ */
2629
+ walkthroughPaused() {
2630
+ return !!(this.walkthroughProgressValue && this.walkthroughProgressValue.paused)
2605
2631
  },
2606
2632
 
2607
2633
  phase() {
@@ -3281,7 +3307,7 @@ export default {
3281
3307
  // the server keeps reporting it unmet — so without this the wizard
3282
3308
  // auto-opens again on every visit. Cancel must not be the only way to
3283
3309
  // get peace.
3284
- this.persistSetupWizardDismissal()
3310
+ this.persistSetupWizardDismissal(true)
3285
3311
  if (this.setupState && typeof this.setupState.refresh === 'function') {
3286
3312
  this.setupState.refresh()
3287
3313
  }
@@ -3302,7 +3328,7 @@ export default {
3302
3328
  * @return {void}
3303
3329
  */
3304
3330
  dismissSetupWizard() {
3305
- this.persistSetupWizardDismissal()
3331
+ this.persistSetupWizardDismissal(false)
3306
3332
  this.setupWizardOpen = false
3307
3333
  },
3308
3334
 
@@ -3325,7 +3351,7 @@ export default {
3325
3351
  * @return {boolean}
3326
3352
  */
3327
3353
  isSetupWizardDismissed() {
3328
- if (this.setupWizardDismissed) {
3354
+ if (this.setupWizardDismissed || this.isSetupWizardDismissedOnServer()) {
3329
3355
  return true
3330
3356
  }
3331
3357
  try {
@@ -3336,18 +3362,87 @@ export default {
3336
3362
  },
3337
3363
 
3338
3364
  /**
3339
- * Record the non-gating setup wizard as dismissed for this manifest
3340
- * `setup.version` so it doesn't auto-open again.
3365
+ * Whether the app's setup status says the wizard was closed, so it
3366
+ * stays closed in every browser and on every device. The status
3367
+ * (`GET /api/setup/status`) may carry `dismissed: true`, or the
3368
+ * `setup.version` it was closed at (`dismissed: 2`), which re-opens the
3369
+ * wizard after a version bump. An app whose status carries neither
3370
+ * keeps today's per-browser behaviour. An app may also answer the
3371
+ * outstanding steps itself when it records the close (OpenRegister's
3372
+ * `dismiss-setup`), which needs nothing here.
3373
+ *
3374
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3375
+ * @return {boolean} True when the server recorded the close for this version.
3376
+ */
3377
+ isSetupWizardDismissedOnServer() {
3378
+ const s = this.setupState
3379
+ const status = s && s.status && s.status.value
3380
+ const dismissed = status && typeof status === 'object' ? status.dismissed : undefined
3381
+ if (dismissed === true) {
3382
+ return true
3383
+ }
3384
+ if (typeof dismissed === 'number' && Number.isFinite(dismissed)) {
3385
+ const version = (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0
3386
+ return dismissed >= version
3387
+ }
3388
+ return false
3389
+ },
3390
+
3391
+ /**
3392
+ * The setup action that records a closed wizard on the server, from
3393
+ * `manifest.setup.dismissAction`, or '' when the app declares none.
3394
+ *
3395
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3396
+ * @return {string} The action id.
3397
+ */
3398
+ setupDismissAction() {
3399
+ const action = this.manifest && this.manifest.setup && this.manifest.setup.dismissAction
3400
+ return typeof action === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(action) ? action : ''
3401
+ },
3402
+
3403
+ /**
3404
+ * Record the setup wizard as closed for this manifest `setup.version`
3405
+ * so it doesn't auto-open again: in this browser (localStorage) and,
3406
+ * when the manifest declares `setup.dismissAction`, on the server
3407
+ * through the app's own setup action endpoint
3408
+ * (`POST /apps/{appId}/api/setup/action/{dismissAction}`, admin-only
3409
+ * like every setup endpoint, so the record is per instance). Then
3410
+ * emits `setup-wizard-dismissed`. Once per page load: finishing
3411
+ * records it, and the result screen's Close that follows does not
3412
+ * post again.
3341
3413
  *
3414
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3415
+ * @param {boolean} [finished] True when the user finished the wizard, false when they closed it.
3342
3416
  * @return {void}
3343
3417
  */
3344
- persistSetupWizardDismissal() {
3418
+ persistSetupWizardDismissal(finished = false) {
3345
3419
  try {
3346
3420
  window.localStorage.setItem(this.setupWizardDismissKey(), '1')
3347
3421
  } catch {
3348
3422
  // Best-effort persistence (private mode / no storage).
3349
3423
  }
3424
+ if (this.setupWizardDismissed) {
3425
+ return
3426
+ }
3350
3427
  this.setupWizardDismissed = true
3428
+ const action = this.setupDismissAction()
3429
+ if (action) {
3430
+ try {
3431
+ Promise.resolve(axios.post(generateUrl(`/apps/${this.appId}/api/setup/action/${action}`), { finished: finished === true }))
3432
+ .catch(() => { /* the local record still holds in this browser */ })
3433
+ } catch {
3434
+ // Never let a failed record break the close.
3435
+ }
3436
+ }
3437
+ /**
3438
+ * @event setup-wizard-dismissed Emitted once when the user closes or finishes the setup wizard.
3439
+ * @type {{ appId: string, version: number, finished: boolean }}
3440
+ */
3441
+ this.$emit('setup-wizard-dismissed', {
3442
+ appId: this.appId,
3443
+ version: (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0,
3444
+ finished: finished === true,
3445
+ })
3351
3446
  },
3352
3447
 
3353
3448
  /**
@@ -3434,6 +3529,49 @@ export default {
3434
3529
  persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3435
3530
  },
3436
3531
 
3532
+ /**
3533
+ * Remember that the user paused the tour, at the step they were on,
3534
+ * so the next page load keeps it hidden until they pick "Continue".
3535
+ *
3536
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
3537
+ * @param {object} [where] `{ tourId, stepId, index }` from CnWalkthrough.
3538
+ * @return {void}
3539
+ */
3540
+ onWalkthroughPause(where) {
3541
+ const current = this.walkthroughProgressValue || {}
3542
+ const tourId = (where && where.tourId) || current.tourId
3543
+ if (!tourId) {
3544
+ return
3545
+ }
3546
+ const value = {
3547
+ tourId,
3548
+ stepId: (where && where.tourId) ? (where.stepId || '') : (current.stepId || ''),
3549
+ index: (where && where.tourId && Number.isInteger(where.index)) ? where.index : (current.index || 0),
3550
+ version: String((this.manifest && this.manifest.version) || ''),
3551
+ paused: true,
3552
+ }
3553
+ this.walkthroughProgressValue = value
3554
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3555
+ },
3556
+
3557
+ /**
3558
+ * Clear the paused mark when the user continues the tour. The step
3559
+ * stays; the next step change writes fresh progress anyway.
3560
+ *
3561
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
3562
+ * @return {void}
3563
+ */
3564
+ unpauseWalkthroughProgress() {
3565
+ const progress = this.walkthroughProgressValue
3566
+ if (!progress || !progress.paused) {
3567
+ return
3568
+ }
3569
+ const value = { ...progress }
3570
+ delete value.paused
3571
+ this.walkthroughProgressValue = value
3572
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3573
+ },
3574
+
3437
3575
  /**
3438
3576
  * Continue an unfinished tour from the user-settings dialog: show a
3439
3577
  * paused tour again, or start the remembered tour at its step.
@@ -3448,10 +3586,11 @@ export default {
3448
3586
  }
3449
3587
  setTimeout(() => {
3450
3588
  const wt = useWalkthrough(this.appId, this.manifest)
3589
+ const progress = this.walkthroughProgressValue
3590
+ this.unpauseWalkthroughProgress()
3451
3591
  if (wt.resumePaused()) {
3452
3592
  return
3453
3593
  }
3454
- const progress = this.walkthroughProgressValue
3455
3594
  if (progress) {
3456
3595
  wt.resumeAt(progress.tourId, progress.stepId)
3457
3596
  }
@@ -3478,6 +3617,7 @@ export default {
3478
3617
  setTimeout(() => {
3479
3618
  const id = this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id
3480
3619
  if (id) {
3620
+ this.unpauseWalkthroughProgress()
3481
3621
  useWalkthrough(this.appId, this.manifest).restart(id)
3482
3622
  }
3483
3623
  }, 50)
@@ -330,24 +330,26 @@
330
330
  v-if="missingRequiredApp(item)"
331
331
  :title="getWidgetTitle(item)"
332
332
  :showTitle="widgetShowTitle(item)">
333
- <NcEmptyContent
334
- :name="installAppLabel(missingRequiredApp(item))"
335
- :description="t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.')"
336
- class="cn-dashboard-page__requires-app cn-requires-app">
337
- <template #icon>
338
- <Download :size="24" />
339
- </template>
340
- <template #action>
341
- <NcButton
342
- variant="primary"
343
- :href="appInstallUrl(missingRequiredApp(item))">
344
- <template #icon>
345
- <Download :size="18" />
346
- </template>
347
- {{ installAppLabel(missingRequiredApp(item)) }}
348
- </NcButton>
349
- </template>
350
- </NcEmptyContent>
333
+ <!-- Compact on purpose: a two-row tile is about 150px, less
334
+ a title row. NcEmptyContent's 64px icon, 20px name,
335
+ description and button did not fit, so the icon ran out
336
+ at the top and the button at the bottom. The button
337
+ already says "Install {app}", so there is no separate
338
+ name, and the text keeps to two lines. -->
339
+ <div class="cn-dashboard-page__requires-app cn-requires-app cn-requires-app--compact"
340
+ data-testid="cn-requires-app">
341
+ <p class="cn-requires-app__description">
342
+ {{ t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.') }}
343
+ </p>
344
+ <NcButton
345
+ variant="primary"
346
+ :href="appInstallUrl(missingRequiredApp(item))">
347
+ <template #icon>
348
+ <Download :size="18" />
349
+ </template>
350
+ {{ installAppLabel(missingRequiredApp(item)) }}
351
+ </NcButton>
352
+ </div>
351
353
  </CnWidgetWrapper>
352
354
 
353
355
  <!-- Tile widget -->
@@ -250,6 +250,17 @@
250
250
  <!-- @binding {object} option The option being rendered (`{ id, label }`). -->
251
251
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
252
252
  </template>
253
+ <!-- NcSelect's own option splits a label in two spans
254
+ (NcEllipsisedOption, keeping its last characters
255
+ visible), and in a narrow dropdown the halves broke
256
+ onto two lines mid-word: "Agricu lture". A plain
257
+ label wraps at spaces only. NcSelectUsers keeps its
258
+ avatar rendering. -->
259
+ <template
260
+ v-else-if="!isUserField(field)"
261
+ #option="optionProps">
262
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
263
+ </template>
253
264
  <template
254
265
  v-if="$slots['field-' + field.key + '-selected-option']"
255
266
  #selected-option="optionProps">
@@ -284,11 +295,17 @@
284
295
  #option="optionProps">
285
296
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
286
297
  </template>
298
+ <template v-else #option="optionProps">
299
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
300
+ </template>
287
301
  <template
288
302
  v-if="$slots['field-' + field.key + '-selected-option']"
289
303
  #selected-option="optionProps">
290
304
  <slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
291
305
  </template>
306
+ <template v-else #selected-option="optionProps">
307
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
308
+ </template>
292
309
  </NcSelect>
293
310
  <CnFieldHelper
294
311
  :text="field.description"
@@ -318,11 +335,17 @@
318
335
  #option="optionProps">
319
336
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
320
337
  </template>
338
+ <template v-else #option="optionProps">
339
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
340
+ </template>
321
341
  <template
322
342
  v-if="$slots['field-' + field.key + '-selected-option']"
323
343
  #selected-option="optionProps">
324
344
  <slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
325
345
  </template>
346
+ <template v-else #selected-option="optionProps">
347
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
348
+ </template>
326
349
  </NcSelect>
327
350
  <CnFieldHelper
328
351
  :text="field.description"
@@ -2452,6 +2475,26 @@ export default {
2452
2475
  * @param {object} field The field definition
2453
2476
  * @return {boolean}
2454
2477
  */
2478
+ /**
2479
+ * The text a select option shows: its label, else a display name, a
2480
+ * name or the id. A plain string option (a tag) is its own text.
2481
+ *
2482
+ * @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
2483
+ * @param {object|string} option The option (slot props).
2484
+ * @return {string} The text.
2485
+ */
2486
+ optionText(option) {
2487
+ if (option === null || option === undefined) {
2488
+ return ''
2489
+ }
2490
+ if (typeof option !== 'object') {
2491
+ return String(option)
2492
+ }
2493
+ const text = [option.label, option.displayName, option.name, option.id]
2494
+ .find((v) => v !== undefined && v !== null && v !== '')
2495
+ return text === undefined ? '' : String(text)
2496
+ },
2497
+
2455
2498
  isUserField(field) {
2456
2499
  return !!(field && field.userPicker && !field.userPicker.multiple)
2457
2500
  },
@@ -3519,8 +3562,15 @@ export default {
3519
3562
  if (this.isIdPickerField(field)) {
3520
3563
  // Reference / user fields store the chosen id (UUID / UID),
3521
3564
  // not the full option. Cache its label so the selection displays.
3565
+ // A user option can carry only `displayName` (NcSelectUsers' own
3566
+ // shape), and without that fallback the chip showed the uid.
3522
3567
  if (option && option.id) {
3523
- this.referenceLabels = { ...this.referenceLabels, [option.id]: option.label || String(option.id) }
3568
+ const name = option.label || option.displayName || this.referenceLabels[option.id]
3569
+ if (name) {
3570
+ this.referenceLabels = { ...this.referenceLabels, [option.id]: name }
3571
+ } else if (this.isUserField(field)) {
3572
+ this.resolveUserLabel(option.id)
3573
+ }
3524
3574
  }
3525
3575
  this.updateField(field.key, option ? option.id : null)
3526
3576
  // Reference options are label-only ({id,label}) — pass the id so
@@ -3587,7 +3637,7 @@ export default {
3587
3637
  const labels = {}
3588
3638
  for (const o of list) {
3589
3639
  if (o && o.id) {
3590
- labels[o.id] = o.label || String(o.id)
3640
+ labels[o.id] = o.label || o.displayName || this.referenceLabels[o.id] || String(o.id)
3591
3641
  }
3592
3642
  }
3593
3643
  if (Object.keys(labels).length > 0) {
@@ -3861,6 +3911,16 @@ export default {
3861
3911
  </script>
3862
3912
 
3863
3913
  <style scoped>
3914
+ /* A select option breaks between words only, never inside one. */
3915
+ .cn-form-dialog__option {
3916
+ display: block;
3917
+ min-width: 0;
3918
+ white-space: normal;
3919
+ overflow-wrap: normal;
3920
+ word-break: normal;
3921
+ hyphens: manual;
3922
+ }
3923
+
3864
3924
  .cn-form-dialog__draft-actions {
3865
3925
  display: flex;
3866
3926
  gap: 8px;
@@ -488,7 +488,7 @@
488
488
  :includeColumns="includeColumns"
489
489
  :columnOverrides="columnOverrides"
490
490
  :rowClass="rowClass"
491
- :filterable="headerFilters && !!effectiveSchema"
491
+ :filterable="tableHeaderFilters"
492
492
  :activeFilters="effectiveActiveFilters"
493
493
  :filterRegister="typeof register === 'string' ? register : ''"
494
494
  @columnFilter="onColumnFilterEvent"
@@ -3873,6 +3873,28 @@ export default {
3873
3873
  },
3874
3874
 
3875
3875
  /** Resolved schema OBJECT (for column generation / icons / labels). */
3876
+ /**
3877
+ * Whether the table shows its header filters. A self-fetching page
3878
+ * applies them itself, with or without a resolved schema (the columns
3879
+ * then say how each one filters, else a text filter on contains). A
3880
+ * host-fed table shows them when a schema came along, as before, or
3881
+ * when the host listens for `filter-change`; a filter nobody applies
3882
+ * would be a control that does nothing.
3883
+ *
3884
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-data-table/spec.md
3885
+ * @return {boolean} True when header filters show.
3886
+ */
3887
+ tableHeaderFilters() {
3888
+ if (!this.headerFilters) {
3889
+ return false
3890
+ }
3891
+ if (this.effectiveSchema || this.isSelfFetchMode) {
3892
+ return true
3893
+ }
3894
+ const vnodeProps = (this.$ && this.$.vnode && this.$.vnode.props) || {}
3895
+ return typeof vnodeProps.onFilterChange === 'function' || Array.isArray(vnodeProps.onFilterChange)
3896
+ },
3897
+
3876
3898
  effectiveSchema() {
3877
3899
  if (this.isSelfFetchMode) {
3878
3900
  return this.list.schema.value
@@ -279,7 +279,7 @@ export default {
279
279
  * @param {(suggestions: Array<object>) => void} callback Receives the suggestion array.
280
280
  */
281
281
  async fetchMentionSuggestions(search, callback) {
282
- const users = await searchNextcloudUsers(search)
282
+ const users = await searchNextcloudUsers(search, { includeCurrentUser: false })
283
283
  callback(users.map((user) => ({
284
284
  id: user.id,
285
285
  label: user.label,
@@ -123,6 +123,13 @@
123
123
 
124
124
  <!-- config-fields -->
125
125
  <template v-else-if="step.type === 'config-fields'">
126
+ <!-- The step's intro, drawn like choice and run-action steps.
127
+ It was left out here, so pipelinq's organisation step
128
+ ("Your organisation details…") showed fields with no
129
+ word about what they were for. -->
130
+ <NcNoteCard v-if="step.body" type="info" data-testid="cn-setup-step-intro">
131
+ {{ stepBody(step) }}
132
+ </NcNoteCard>
126
133
  <div
127
134
  v-for="field in fieldsFor(step)"
128
135
  :key="field.key"
@@ -172,6 +172,12 @@ export default {
172
172
  tourId: { type: String, default: '' },
173
173
  /** Optional resume token `{ tourId, stepId }` (refresh / cross-app hand-off). */
174
174
  resume: { type: Object, default: null },
175
+ /**
176
+ * Whether a first-visit tour may start on its own. The host turns it
177
+ * off while the user's tour is paused, so a paused tour stays hidden
178
+ * across page loads until the user picks "Continue".
179
+ */
180
+ autoStart: { type: Boolean, default: true },
175
181
  /** Stacking order of the overlay. */
176
182
  zIndex: { type: Number, default: 10000 },
177
183
  /** Next button label. */
@@ -571,6 +577,9 @@ export default {
571
577
  this.wt.start(this.tourId)
572
578
  return
573
579
  }
580
+ if (!this.autoStart) {
581
+ return
582
+ }
574
583
  const auto = this.wt.autoStartTour.value
575
584
  if (!auto) {
576
585
  return
@@ -1171,31 +1180,43 @@ export default {
1171
1180
  },
1172
1181
 
1173
1182
  /**
1174
- * End the tour for good from the corner close button: mark it complete so
1175
- * the seen-version is persisted and it does not auto-show again.
1183
+ * The corner close button PAUSES the tour, like ESC and the dim. It
1184
+ * used to complete it, and the host then wiped the saved step, so a
1185
+ * restart began at step 1 again. Only Finish on the last step (and a
1186
+ * host-supplied Skip) completes the tour; "Start over" in the user
1187
+ * settings stays the way back to step 1.
1176
1188
  *
1189
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
1177
1190
  * @return {void}
1178
1191
  */
1179
1192
  close() {
1180
- this.wt.complete()
1181
- this.$emit('complete')
1193
+ this.onBackdrop()
1182
1194
  },
1183
1195
 
1184
1196
  /**
1185
- * Pause the tour from a backdrop click or ESC. The tour hides and
1186
- * keeps its step; only Skip (the close button) and Finish end it.
1197
+ * Pause the tour from the corner X, a backdrop click or ESC. The tour
1198
+ * hides and keeps its step; only Finish (or a host Skip) ends it.
1187
1199
  * Pressing ESC used to dismiss it, and the host recorded that as
1188
- * "seen", so the tour never came back.
1200
+ * "seen", so the tour never came back. The `pause` event carries
1201
+ * where the user was, so the host can keep a paused tour hidden on
1202
+ * the next page load.
1189
1203
  *
1190
1204
  * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
1205
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
1191
1206
  * @return {void}
1192
1207
  */
1193
1208
  onBackdrop() {
1209
+ const where = {
1210
+ tourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',
1211
+ stepId: (this.step && this.step.id) || '',
1212
+ index: this.index,
1213
+ }
1194
1214
  this.wt.pause()
1195
1215
  /**
1196
- * @event pause Emitted when the user hides the tour (backdrop / ESC). The tour can continue later.
1216
+ * @event pause Emitted when the user hides the tour (X, backdrop, ESC). The tour can continue later.
1217
+ * @type {{ tourId: string, stepId: string, index: number }}
1197
1218
  */
1198
- this.$emit('pause')
1219
+ this.$emit('pause', where)
1199
1220
  },
1200
1221
  },
1201
1222
  }