@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.
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +38 -42
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +22 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/composables/useWalkthrough.js +11 -3
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +53 -0
- package/dist/esm/utils/columnFilters.js +54 -4
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/userAutocomplete.js +69 -3
- package/dist/esm/utils/userAutocomplete.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +615 -235
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +53 -0
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
- package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -18
- package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +23 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
- package/src/composables/useWalkthrough.js +11 -3
- package/src/css/dashboard.css +44 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/columnFilters.js +54 -4
- package/src/utils/userAutocomplete.js +68 -2
- 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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35657637 bytes
|
|
4
|
+
sha256: b7b966a528988db88e213ce0afc88832aae9c6b60341877ba5226dc3d833f211
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
*
|
|
3340
|
-
*
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
*
|
|
1175
|
-
*
|
|
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.
|
|
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
|
|
1186
|
-
* keeps its step; only
|
|
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
|
|
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
|
}
|