@conduction/nextcloud-vue 2.62.0 → 2.63.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/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +271 -32
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +216 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +43 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +43 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +449 -0
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +9369 -5193
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +449 -0
- package/l10n/en.json +43 -0
- package/l10n/nl.json +43 -0
- package/package.json +1 -1
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +271 -32
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +260 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +19 -0
- package/src/css/table.css +81 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -25,7 +25,12 @@
|
|
|
25
25
|
the greyed-out area reaches the app, which is the point. A centered
|
|
26
26
|
step spotlights nothing, so its backdrop stays interactive and
|
|
27
27
|
dismisses the tour. -->
|
|
28
|
-
|
|
28
|
+
<!-- Quiet: an app dialog is open, or the step's target belongs to
|
|
29
|
+
another page. No dim and no cutout, so the overlay never sits on
|
|
30
|
+
top of the dialog and never invites a click on the wrong page.
|
|
31
|
+
Only the docked coachmark stays. -->
|
|
32
|
+
<template v-if="quiet" />
|
|
33
|
+
<template v-else-if="isCentered || !rect">
|
|
29
34
|
<div v-if="isCentered"
|
|
30
35
|
class="cn-walkthrough__dim cn-walkthrough__dim--full"
|
|
31
36
|
@click.self="onBackdrop" />
|
|
@@ -50,7 +55,7 @@
|
|
|
50
55
|
<!-- Coachmark card -->
|
|
51
56
|
<div ref="card"
|
|
52
57
|
class="cn-walkthrough__card"
|
|
53
|
-
:class="`cn-walkthrough__card--${cardPlacement}
|
|
58
|
+
:class="[`cn-walkthrough__card--${cardPlacement}`, { 'cn-walkthrough__card--docked': quiet }]"
|
|
54
59
|
:style="cardStyle">
|
|
55
60
|
<NcButton class="cn-walkthrough__close"
|
|
56
61
|
variant="tertiary"
|
|
@@ -183,7 +188,7 @@ export default {
|
|
|
183
188
|
translate: { type: Function, default: null },
|
|
184
189
|
},
|
|
185
190
|
|
|
186
|
-
emits: ['complete', 'dismiss', 'step-change', 'advance', 'handoff'],
|
|
191
|
+
emits: ['complete', 'dismiss', 'pause', 'progress', 'step-change', 'advance', 'handoff'],
|
|
187
192
|
|
|
188
193
|
setup(props) {
|
|
189
194
|
const wt = useWalkthrough(props.appId, props.manifest, {
|
|
@@ -200,6 +205,10 @@ export default {
|
|
|
200
205
|
cardPos: { top: 0, left: 0 },
|
|
201
206
|
cardPlacement: 'bottom',
|
|
202
207
|
targetEl: null,
|
|
208
|
+
// An app dialog (NcModal / NcDialog) is open over the page.
|
|
209
|
+
appModalOpen: false,
|
|
210
|
+
// The step's target was found on another page than the current one.
|
|
211
|
+
offPage: false,
|
|
203
212
|
}
|
|
204
213
|
},
|
|
205
214
|
|
|
@@ -228,6 +237,18 @@ export default {
|
|
|
228
237
|
return this.wt.isLast.value
|
|
229
238
|
},
|
|
230
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Whether the overlay steps back: no dim, no cutout, a docked card.
|
|
242
|
+
* True while an app dialog is open or the step's target is on
|
|
243
|
+
* another page.
|
|
244
|
+
*
|
|
245
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
246
|
+
* @return {boolean}
|
|
247
|
+
*/
|
|
248
|
+
quiet() {
|
|
249
|
+
return this.appModalOpen || this.offPage
|
|
250
|
+
},
|
|
251
|
+
|
|
231
252
|
isCentered() {
|
|
232
253
|
return this.step && this.step.placement === 'center'
|
|
233
254
|
},
|
|
@@ -321,6 +342,9 @@ export default {
|
|
|
321
342
|
},
|
|
322
343
|
|
|
323
344
|
cardStyle() {
|
|
345
|
+
if (this.quiet) {
|
|
346
|
+
return {}
|
|
347
|
+
}
|
|
324
348
|
if (this.isCentered || !this.rect) {
|
|
325
349
|
return { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }
|
|
326
350
|
}
|
|
@@ -339,6 +363,18 @@ export default {
|
|
|
339
363
|
* @type {{ stepId: string, index: number }}
|
|
340
364
|
*/
|
|
341
365
|
this.$emit('step-change', { stepId: newStep.id, index: this.index })
|
|
366
|
+
/**
|
|
367
|
+
* @event progress Emitted on every step change so the host can remember where the user is.
|
|
368
|
+
* @type {{ tourId: string, stepId: string, index: number }}
|
|
369
|
+
*/
|
|
370
|
+
this.$emit('progress', {
|
|
371
|
+
tourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',
|
|
372
|
+
stepId: newStep.id,
|
|
373
|
+
index: this.index,
|
|
374
|
+
})
|
|
375
|
+
// A new step finds its own page again.
|
|
376
|
+
this._homeRoute = null
|
|
377
|
+
this.offPage = false
|
|
342
378
|
this.$nextTick(() => this.locateTarget())
|
|
343
379
|
}
|
|
344
380
|
},
|
|
@@ -377,6 +413,9 @@ export default {
|
|
|
377
413
|
// A qualifying auto-start tour whose first-step page is NOT the current
|
|
378
414
|
// route — held here until the user navigates to that page (see hookRouter).
|
|
379
415
|
this._pendingAutoTour = null
|
|
416
|
+
// The route the active step's target was first found on (see locateTarget).
|
|
417
|
+
this._homeRoute = null
|
|
418
|
+
this._modalObs = null
|
|
380
419
|
},
|
|
381
420
|
|
|
382
421
|
mounted() {
|
|
@@ -386,12 +425,27 @@ export default {
|
|
|
386
425
|
window.addEventListener('scroll', this._onScroll, true)
|
|
387
426
|
window.addEventListener('resize', this._onScroll)
|
|
388
427
|
this._onKey = (e) => {
|
|
389
|
-
if (e.key
|
|
390
|
-
|
|
428
|
+
if (e.key !== 'Escape' || !this.active) {
|
|
429
|
+
return
|
|
391
430
|
}
|
|
431
|
+
// ESC while an app dialog is open belongs to that dialog. It used
|
|
432
|
+
// to dismiss the tour too, so closing a create form ended it.
|
|
433
|
+
if (this.isAppModalOpen()) {
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
this.onBackdrop()
|
|
392
437
|
}
|
|
393
438
|
window.addEventListener('keydown', this._onKey)
|
|
394
|
-
this._onObjectCreated = (e) =>
|
|
439
|
+
this._onObjectCreated = (e) => {
|
|
440
|
+
const d = (e && e.detail) || {}
|
|
441
|
+
this.wt.notify({
|
|
442
|
+
kind: 'object-created',
|
|
443
|
+
register: d.register,
|
|
444
|
+
schema: d.schema,
|
|
445
|
+
object: d.object && typeof d.object === 'object' ? d.object : d,
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
this.watchAppModals()
|
|
395
449
|
window.addEventListener('cn-walkthrough:object-created', this._onObjectCreated)
|
|
396
450
|
this.hookRouter()
|
|
397
451
|
if (this.active) {
|
|
@@ -404,6 +458,10 @@ export default {
|
|
|
404
458
|
window.removeEventListener('resize', this._onScroll)
|
|
405
459
|
window.removeEventListener('keydown', this._onKey)
|
|
406
460
|
window.removeEventListener('cn-walkthrough:object-created', this._onObjectCreated)
|
|
461
|
+
if (this._modalObs) {
|
|
462
|
+
this._modalObs.disconnect()
|
|
463
|
+
this._modalObs = null
|
|
464
|
+
}
|
|
407
465
|
if (typeof this._routeUnhook === 'function') {
|
|
408
466
|
this._routeUnhook()
|
|
409
467
|
}
|
|
@@ -411,6 +469,51 @@ export default {
|
|
|
411
469
|
},
|
|
412
470
|
|
|
413
471
|
methods: {
|
|
472
|
+
/**
|
|
473
|
+
* Whether an app dialog (NcModal / NcDialog, or any other modal
|
|
474
|
+
* dialog that is not this overlay) is open.
|
|
475
|
+
*
|
|
476
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
477
|
+
* @return {boolean}
|
|
478
|
+
*/
|
|
479
|
+
isAppModalOpen() {
|
|
480
|
+
if (typeof document === 'undefined') {
|
|
481
|
+
return false
|
|
482
|
+
}
|
|
483
|
+
const own = this.$el
|
|
484
|
+
const nodes = document.querySelectorAll('.modal-mask, [role="dialog"][aria-modal="true"]')
|
|
485
|
+
for (const node of nodes) {
|
|
486
|
+
if (own && typeof own.contains === 'function' && (own === node || own.contains(node))) {
|
|
487
|
+
continue
|
|
488
|
+
}
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
return false
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Follow app dialogs opening and closing, so the overlay steps back
|
|
496
|
+
* while one is open and comes back after.
|
|
497
|
+
*
|
|
498
|
+
* @return {void}
|
|
499
|
+
*/
|
|
500
|
+
watchAppModals() {
|
|
501
|
+
const update = () => {
|
|
502
|
+
const open = this.isAppModalOpen()
|
|
503
|
+
if (open !== this.appModalOpen) {
|
|
504
|
+
this.appModalOpen = open
|
|
505
|
+
if (!open && this.active) {
|
|
506
|
+
this.$nextTick(() => this.locateTarget())
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
update()
|
|
511
|
+
if (typeof window !== 'undefined' && window.MutationObserver) {
|
|
512
|
+
this._modalObs = new MutationObserver(update)
|
|
513
|
+
this._modalObs.observe(document.body, { childList: true, subtree: true })
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
|
|
414
517
|
/**
|
|
415
518
|
* Translate an i18n key/text via the `translate` prop when given.
|
|
416
519
|
*
|
|
@@ -598,6 +701,23 @@ export default {
|
|
|
598
701
|
return
|
|
599
702
|
}
|
|
600
703
|
const el = this.resolveTarget(this.step)
|
|
704
|
+
// A step's target belongs to the page it was first found on. The
|
|
705
|
+
// same `index-add` button exists on every list page, and a step
|
|
706
|
+
// left half-done on Products used to spotlight the Clients page's
|
|
707
|
+
// button too, inviting a click that belonged to another step.
|
|
708
|
+
const kind = (this.step.target && this.step.target.kind) || ''
|
|
709
|
+
const routeName = this.$route ? this.$route.name : null
|
|
710
|
+
if (el && kind !== 'page' && kind !== 'nav-item' && routeName) {
|
|
711
|
+
if (this._homeRoute === null) {
|
|
712
|
+
this._homeRoute = routeName
|
|
713
|
+
} else if (this._homeRoute !== routeName) {
|
|
714
|
+
this.offPage = true
|
|
715
|
+
this.targetEl = null
|
|
716
|
+
this.rect = null
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
this.offPage = false
|
|
601
721
|
this.targetEl = el
|
|
602
722
|
if (!el) {
|
|
603
723
|
// Optional step whose target is absent → skip; else wait for it.
|
|
@@ -862,6 +982,10 @@ export default {
|
|
|
862
982
|
* @return {void}
|
|
863
983
|
*/
|
|
864
984
|
focusCard() {
|
|
985
|
+
// Never take focus from an open app dialog.
|
|
986
|
+
if (this.appModalOpen) {
|
|
987
|
+
return
|
|
988
|
+
}
|
|
865
989
|
this.$nextTick(() => {
|
|
866
990
|
const btn = this.$refs.firstBtn && this.$refs.firstBtn.$el
|
|
867
991
|
if (btn && typeof btn.focus === 'function') {
|
|
@@ -1058,16 +1182,20 @@ export default {
|
|
|
1058
1182
|
},
|
|
1059
1183
|
|
|
1060
1184
|
/**
|
|
1061
|
-
*
|
|
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.
|
|
1187
|
+
* Pressing ESC used to dismiss it, and the host recorded that as
|
|
1188
|
+
* "seen", so the tour never came back.
|
|
1062
1189
|
*
|
|
1190
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
1063
1191
|
* @return {void}
|
|
1064
1192
|
*/
|
|
1065
1193
|
onBackdrop() {
|
|
1066
|
-
this.wt.
|
|
1194
|
+
this.wt.pause()
|
|
1067
1195
|
/**
|
|
1068
|
-
* @event
|
|
1196
|
+
* @event pause Emitted when the user hides the tour (backdrop / ESC). The tour can continue later.
|
|
1069
1197
|
*/
|
|
1070
|
-
this.$emit('
|
|
1198
|
+
this.$emit('pause')
|
|
1071
1199
|
},
|
|
1072
1200
|
},
|
|
1073
1201
|
}
|
|
@@ -1126,6 +1254,11 @@ export default {
|
|
|
1126
1254
|
pointer-events: auto;
|
|
1127
1255
|
}
|
|
1128
1256
|
|
|
1257
|
+
.cn-walkthrough__card--docked {
|
|
1258
|
+
inset-block-end: 16px;
|
|
1259
|
+
inset-inline-start: 16px;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1129
1262
|
.cn-walkthrough__close {
|
|
1130
1263
|
/* !important beats NcButton's own `position: relative`, which would otherwise
|
|
1131
1264
|
leave the close button in-flow at the card's top-left. */
|
|
@@ -25,6 +25,7 @@ import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
|
25
25
|
import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
|
|
26
26
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
27
27
|
import CnTextWidget from '../CnTextWidget/CnTextWidget.vue'
|
|
28
|
+
import CnTimelineWidget from '../CnTimelineWidget/CnTimelineWidget.vue'
|
|
28
29
|
import CnWidgetCardGrid from '../CnWidgetCardGrid/CnWidgetCardGrid.vue'
|
|
29
30
|
import CnWidgetFormRenderer from '../CnWidgetFormRenderer/CnWidgetFormRenderer.vue'
|
|
30
31
|
import CnWidgetMapViewer from '../CnWidgetMapViewer/CnWidgetMapViewer.vue'
|
|
@@ -84,6 +85,7 @@ export const BUILT_IN_WIDGETS = {
|
|
|
84
85
|
integration: CnIntegrationWidget,
|
|
85
86
|
banner: CnBannerWidget,
|
|
86
87
|
'audit-trail': CnAuditTrailWidget,
|
|
88
|
+
timeline: CnTimelineWidget,
|
|
87
89
|
header: CnHeaderWidget,
|
|
88
90
|
text: CnTextWidget,
|
|
89
91
|
divider: CnDividerWidget,
|
|
@@ -72,6 +72,7 @@ import '../CnObjectDataWidget/dashboardRegistration.js'
|
|
|
72
72
|
import '../CnObjectGeoWidget/dashboardRegistration.js'
|
|
73
73
|
import '../CnWidgetObjectTable/dashboardRegistration.js'
|
|
74
74
|
import '../CnAuditTrailWidget/dashboardRegistration.js'
|
|
75
|
+
import '../CnTimelineWidget/dashboardRegistration.js'
|
|
75
76
|
import '../CnKbSearchWidget/index.js'
|
|
76
77
|
import '../CnInteractionFormWidget/index.js'
|
|
77
78
|
import '../CnBannerWidget/index.js'
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
</NcButton>
|
|
106
106
|
<NcButton variant="primary"
|
|
107
107
|
data-testid="cn-wizard-next"
|
|
108
|
-
:disabled="loading"
|
|
108
|
+
:disabled="loading || nextDisabled"
|
|
109
109
|
@click="isLast ? submit() : next()">
|
|
110
110
|
<template #icon>
|
|
111
111
|
<NcLoadingIcon v-if="loading" :size="20" />
|
|
@@ -280,6 +280,19 @@ export default {
|
|
|
280
280
|
type: Boolean,
|
|
281
281
|
default: true,
|
|
282
282
|
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Disable the Next / Submit button, whatever the step's `validate`
|
|
286
|
+
* would say. For a host that knows the wizard cannot go on yet, such as
|
|
287
|
+
* `CnSetupWizard` while a required app is missing.
|
|
288
|
+
*
|
|
289
|
+
* @type {boolean}
|
|
290
|
+
* @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
|
|
291
|
+
*/
|
|
292
|
+
nextDisabled: {
|
|
293
|
+
type: Boolean,
|
|
294
|
+
default: false,
|
|
295
|
+
},
|
|
283
296
|
},
|
|
284
297
|
|
|
285
298
|
emits: ['close', 'step-change', 'submit'],
|
|
@@ -396,6 +409,9 @@ export default {
|
|
|
396
409
|
* @return {Promise<void>}
|
|
397
410
|
*/
|
|
398
411
|
async next() {
|
|
412
|
+
if (this.nextDisabled) {
|
|
413
|
+
return
|
|
414
|
+
}
|
|
399
415
|
if (this.isLast) {
|
|
400
416
|
return this.submit()
|
|
401
417
|
}
|
|
@@ -467,6 +483,9 @@ export default {
|
|
|
467
483
|
* @return {Promise<void>}
|
|
468
484
|
*/
|
|
469
485
|
async submit() {
|
|
486
|
+
if (this.nextDisabled) {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
470
489
|
const ok = await this.runValidation()
|
|
471
490
|
if (!ok) {
|
|
472
491
|
return
|
package/src/components/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { CnLeafDependencySettings } from './CnLeafDependencySettings/index.js'
|
|
|
17
17
|
export { CnStatsBlock } from './CnStatsBlock/index.js'
|
|
18
18
|
export { CnStructuredDocReview } from './CnStructuredDocReview/index.js'
|
|
19
19
|
export { CnConfigurationCard } from './CnConfigurationCard/index.js'
|
|
20
|
+
export { CnAdminActionCard } from './CnAdminActionCard/index.js'
|
|
20
21
|
export { CnVersionInfoCard } from './CnVersionInfoCard/index.js'
|
|
21
22
|
export { CnAdminSettingsShell } from './CnAdminSettingsShell/index.js'
|
|
22
23
|
export { CnCellRenderer } from './CnCellRenderer/index.js'
|
|
@@ -103,6 +104,7 @@ export { CnSearchPage } from './CnSearchPage/index.js'
|
|
|
103
104
|
export { CnCommandPalette } from './CnCommandPalette/index.js'
|
|
104
105
|
export { CnTabbedFormDialog } from './CnTabbedFormDialog/index.js'
|
|
105
106
|
export { CnTimelineStages } from './CnTimelineStages/index.js'
|
|
107
|
+
export { CnTimelineWidget } from './CnTimelineWidget/index.js'
|
|
106
108
|
export { CnTreeView } from './CnTreeView/index.js'
|
|
107
109
|
export { CnUserActionMenu } from './CnUserActionMenu/index.js'
|
|
108
110
|
export { CnNotesCard } from './CnNotesCard/index.js'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useAppStatus } from './useAppStatus.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve `manifest.dependencies` entries to their live status.
|
|
10
|
+
*
|
|
11
|
+
* Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)
|
|
12
|
+
* or `{ id, name?, required? }` where `required` defaults to true. Each entry
|
|
13
|
+
* becomes `{ id, name, required, installed, enabled }`.
|
|
14
|
+
*
|
|
15
|
+
* `serverStatuses` is the optional `dependency_statuses` initial state an app
|
|
16
|
+
* injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over
|
|
17
|
+
* the browser heuristic, because only the server can tell "installed but
|
|
18
|
+
* disabled" from "not installed". Without it, `useAppStatus` answers, which
|
|
19
|
+
* is the same resolution `CnAppRoot` uses for its dependency-missing phase.
|
|
20
|
+
*
|
|
21
|
+
* @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.
|
|
22
|
+
* @param {object} [serverStatuses] Server-reported statuses keyed by app id.
|
|
23
|
+
* @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.
|
|
24
|
+
*
|
|
25
|
+
* @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const missing = checkDependencies(manifest.dependencies)
|
|
29
|
+
* .filter((d) => d.required && !(d.installed && d.enabled))
|
|
30
|
+
*/
|
|
31
|
+
export function checkDependencies(entries, serverStatuses = {}) {
|
|
32
|
+
const list = Array.isArray(entries) ? entries : []
|
|
33
|
+
const server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}
|
|
34
|
+
return list
|
|
35
|
+
.map((entry) => {
|
|
36
|
+
const isObject = entry !== null && typeof entry === 'object'
|
|
37
|
+
const id = isObject ? entry.id : entry
|
|
38
|
+
if (typeof id !== 'string' || id === '') {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
const required = isObject ? entry.required !== false : true
|
|
42
|
+
const name = (isObject && entry.name) || id
|
|
43
|
+
const reported = server[id]
|
|
44
|
+
if (reported !== undefined && reported !== null) {
|
|
45
|
+
return { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }
|
|
46
|
+
}
|
|
47
|
+
const status = useAppStatus(id)
|
|
48
|
+
return { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }
|
|
49
|
+
})
|
|
50
|
+
.filter((row) => row !== null)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether a resolved dependency row is usable: installed AND enabled.
|
|
55
|
+
*
|
|
56
|
+
* @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.
|
|
57
|
+
* @return {boolean} True when the app can be used.
|
|
58
|
+
*/
|
|
59
|
+
export function isDependencyResolved(row) {
|
|
60
|
+
return !!row && row.installed === true && row.enabled === true
|
|
61
|
+
}
|
|
@@ -199,6 +199,162 @@ export function persistWalkthroughSeenVersion(appId, configKey, version, options
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* localStorage key prefix for the per-browser mirror of tour progress.
|
|
204
|
+
*/
|
|
205
|
+
export const WALKTHROUGH_PROGRESS_STORAGE_PREFIX = 'cn-walkthrough-progress:'
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The preference key that holds tour progress, next to the completion key.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
211
|
+
* @return {string} `<configKey>-progress`, or '' when no key is declared.
|
|
212
|
+
*/
|
|
213
|
+
export function walkthroughProgressKey(configKey) {
|
|
214
|
+
return configKey ? configKey + '-progress' : ''
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Normalise stored progress (an object, or the JSON string a preference
|
|
219
|
+
* holds) into `{ tourId, stepId, index, version }`, or null.
|
|
220
|
+
*
|
|
221
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
222
|
+
* @param {unknown} value The stored value.
|
|
223
|
+
* @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.
|
|
224
|
+
*/
|
|
225
|
+
export function normaliseWalkthroughProgress(value) {
|
|
226
|
+
let data = value
|
|
227
|
+
if (typeof data === 'string') {
|
|
228
|
+
if (data === '') {
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
data = JSON.parse(data)
|
|
233
|
+
} catch {
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {
|
|
238
|
+
return null
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
tourId: data.tourId,
|
|
242
|
+
stepId: typeof data.stepId === 'string' ? data.stepId : '',
|
|
243
|
+
index: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,
|
|
244
|
+
version: typeof data.version === 'string' ? data.version : '',
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Read the per-browser mirror of the tour progress. Synchronous.
|
|
250
|
+
*
|
|
251
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
252
|
+
* @param {string} appId The Nextcloud app id.
|
|
253
|
+
* @param {Storage} [storage] Injectable storage backend.
|
|
254
|
+
* @return {object|null} The progress, or null.
|
|
255
|
+
*/
|
|
256
|
+
export function readLocalWalkthroughProgress(appId, storage) {
|
|
257
|
+
const s = resolveStorage(storage)
|
|
258
|
+
if (!s) {
|
|
259
|
+
return null
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
return normaliseWalkthroughProgress(s.getItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId))
|
|
263
|
+
} catch {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Load the user's tour progress: the per-user preference
|
|
270
|
+
* `<completionConfigKey>-progress` when declared, else the local mirror.
|
|
271
|
+
*
|
|
272
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
273
|
+
* @param {string} appId The Nextcloud app id.
|
|
274
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
275
|
+
* @param {object} [options] `{ http, storage }` injection points for tests.
|
|
276
|
+
* @return {Promise<object|null>} The progress, or null.
|
|
277
|
+
*/
|
|
278
|
+
export async function loadWalkthroughProgress(appId, configKey, options = {}) {
|
|
279
|
+
const local = readLocalWalkthroughProgress(appId, options.storage)
|
|
280
|
+
const key = walkthroughProgressKey(configKey)
|
|
281
|
+
if (!key) {
|
|
282
|
+
return local
|
|
283
|
+
}
|
|
284
|
+
const http = options.http || axios
|
|
285
|
+
try {
|
|
286
|
+
const { data } = await http.get(walkthroughPreferenceUrl(appId, key))
|
|
287
|
+
if (!isPlainObject(data) || !('value' in data)) {
|
|
288
|
+
return local
|
|
289
|
+
}
|
|
290
|
+
return normaliseWalkthroughProgress(data.value) || local
|
|
291
|
+
} catch {
|
|
292
|
+
return local
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Persist the user's tour progress, or clear it with `null`. Writes the
|
|
298
|
+
* local mirror and, with a completion key, the per-user preference. Never
|
|
299
|
+
* throws.
|
|
300
|
+
*
|
|
301
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
302
|
+
* @param {string} appId The Nextcloud app id.
|
|
303
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
304
|
+
* @param {object|null} progress `{ tourId, stepId, index, version }`, or null to clear.
|
|
305
|
+
* @param {object} [options] `{ http, storage }` injection points for tests.
|
|
306
|
+
* @return {Promise<boolean>} True when the server preference was written.
|
|
307
|
+
*/
|
|
308
|
+
export function persistWalkthroughProgress(appId, configKey, progress, options = {}) {
|
|
309
|
+
const value = progress ? normaliseWalkthroughProgress(progress) : null
|
|
310
|
+
const raw = value ? JSON.stringify(value) : ''
|
|
311
|
+
const s = resolveStorage(options.storage)
|
|
312
|
+
if (s) {
|
|
313
|
+
try {
|
|
314
|
+
if (raw) {
|
|
315
|
+
s.setItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId, raw)
|
|
316
|
+
} else {
|
|
317
|
+
s.removeItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId)
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
/* quota / private mode: best-effort */
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const key = walkthroughProgressKey(configKey)
|
|
324
|
+
if (!key) {
|
|
325
|
+
return Promise.resolve(false)
|
|
326
|
+
}
|
|
327
|
+
const http = options.http || axios
|
|
328
|
+
try {
|
|
329
|
+
return Promise.resolve(http.put(walkthroughPreferenceUrl(appId, key), { value: raw }))
|
|
330
|
+
.then(() => true)
|
|
331
|
+
.catch(() => false)
|
|
332
|
+
} catch {
|
|
333
|
+
return Promise.resolve(false)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Whether an `object-created` signal satisfies a step's `advanceOn`.
|
|
339
|
+
*
|
|
340
|
+
* The signal's own `register` / `schema` (the slugs the creating surface
|
|
341
|
+
* knows) are compared first; an object's `@self` values are numeric ids on
|
|
342
|
+
* OpenRegister and only match a step that also uses ids.
|
|
343
|
+
*
|
|
344
|
+
* @param {object} advanceOn The step's `advanceOn`.
|
|
345
|
+
* @param {object} signal `{ register?, schema?, object? }`.
|
|
346
|
+
* @return {boolean} True when it matches.
|
|
347
|
+
*/
|
|
348
|
+
function objectCreatedMatches(advanceOn, signal) {
|
|
349
|
+
const obj = signal.object || {}
|
|
350
|
+
const self = obj['@self'] || {}
|
|
351
|
+
const same = (want, candidates) => !want || candidates
|
|
352
|
+
.filter((c) => c !== null && c !== undefined && c !== '')
|
|
353
|
+
.some((c) => String(c).toLowerCase() === String(want).toLowerCase())
|
|
354
|
+
return same(advanceOn.register, [signal.register, obj.register, self.register])
|
|
355
|
+
&& same(advanceOn.schema, [signal.schema, obj.schema, self.schema])
|
|
356
|
+
}
|
|
357
|
+
|
|
202
358
|
/**
|
|
203
359
|
* Compare two semver-ish strings. Missing / unparseable parts sort as 0.
|
|
204
360
|
*
|
|
@@ -276,6 +432,9 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
276
432
|
const currentIndex = ref(0)
|
|
277
433
|
const context = ref({})
|
|
278
434
|
const running = ref(false)
|
|
435
|
+
// True while the tour is hidden by ESC or a backdrop click. The tour
|
|
436
|
+
// and its step stay, so it continues where it was.
|
|
437
|
+
const paused = ref(false)
|
|
279
438
|
// True while a user-initiated replay is active (the "Restart tutorial"
|
|
280
439
|
// entry). A replay shows the FULL tour regardless of the persisted
|
|
281
440
|
// seen-version — otherwise a returning user, whose seenVersion already
|
|
@@ -479,6 +638,7 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
479
638
|
*/
|
|
480
639
|
function start(tourId, startIndex = 0) {
|
|
481
640
|
replaying.value = false
|
|
641
|
+
paused.value = false
|
|
482
642
|
activeTourId.value = tourId
|
|
483
643
|
currentIndex.value = startIndex
|
|
484
644
|
context.value = {}
|
|
@@ -503,9 +663,59 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
503
663
|
*/
|
|
504
664
|
function dismiss() {
|
|
505
665
|
running.value = false
|
|
666
|
+
paused.value = false
|
|
506
667
|
activeTourId.value = null
|
|
507
668
|
replaying.value = false
|
|
508
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Hide the active tour without ending it (ESC, a click on the dim).
|
|
672
|
+
* The tour and step are kept, so `resumePaused()` continues there.
|
|
673
|
+
*
|
|
674
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
675
|
+
* @return {void}
|
|
676
|
+
*/
|
|
677
|
+
function pause() {
|
|
678
|
+
if (!activeTourId.value) {
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
running.value = false
|
|
682
|
+
paused.value = true
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Show a paused tour again at the step it was on.
|
|
686
|
+
*
|
|
687
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
688
|
+
* @return {boolean} True when there was a paused tour to show.
|
|
689
|
+
*/
|
|
690
|
+
function resumePaused() {
|
|
691
|
+
if (!paused.value || !activeTourId.value) {
|
|
692
|
+
return false
|
|
693
|
+
}
|
|
694
|
+
paused.value = false
|
|
695
|
+
running.value = true
|
|
696
|
+
return true
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Start a tour at a remembered step ("continue where you left off").
|
|
700
|
+
* Runs in replay mode so a returning user sees the full tour, and
|
|
701
|
+
* falls back to the first step when the step no longer exists.
|
|
702
|
+
*
|
|
703
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
704
|
+
* @param {string} tourId The tour id.
|
|
705
|
+
* @param {string} [stepId] The step to continue at.
|
|
706
|
+
* @return {boolean} True when the tour exists and started.
|
|
707
|
+
*/
|
|
708
|
+
function resumeAt(tourId, stepId) {
|
|
709
|
+
const tour = tours.find((t) => t.id === tourId)
|
|
710
|
+
if (!tour) {
|
|
711
|
+
return false
|
|
712
|
+
}
|
|
713
|
+
replaying.value = true
|
|
714
|
+
const idx = composeSteps(tour).findIndex((s) => s.id === stepId)
|
|
715
|
+
start(tourId, Math.max(0, idx))
|
|
716
|
+
replaying.value = true
|
|
717
|
+
return true
|
|
718
|
+
}
|
|
509
719
|
/**
|
|
510
720
|
* Mark the active tour complete: stop running and invoke the
|
|
511
721
|
* `onComplete(appVersion)` persistence callback so the seen version is
|
|
@@ -515,6 +725,7 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
515
725
|
*/
|
|
516
726
|
function complete() {
|
|
517
727
|
running.value = false
|
|
728
|
+
paused.value = false
|
|
518
729
|
activeTourId.value = null
|
|
519
730
|
replaying.value = false
|
|
520
731
|
if (typeof options.onComplete === 'function') {
|
|
@@ -542,12 +753,9 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
542
753
|
runCapture(step, { params: signal.params || {} })
|
|
543
754
|
}
|
|
544
755
|
} else if (signal.kind === 'object-created' && a.type === 'object-created') {
|
|
545
|
-
|
|
546
|
-
const reg = obj.register ?? obj['@self']?.register
|
|
547
|
-
const sch = obj.schema ?? obj['@self']?.schema
|
|
548
|
-
match = (!a.register || reg === a.register) && (!a.schema || sch === a.schema)
|
|
756
|
+
match = objectCreatedMatches(a, signal)
|
|
549
757
|
if (match) {
|
|
550
|
-
runCapture(step, { object:
|
|
758
|
+
runCapture(step, { object: signal.object || {} })
|
|
551
759
|
}
|
|
552
760
|
} else if (signal.kind === 'element' && a.type === 'element-appears') {
|
|
553
761
|
match = true
|
|
@@ -575,9 +783,13 @@ export function useWalkthrough(appId, manifest, options = {}) {
|
|
|
575
783
|
isLast,
|
|
576
784
|
context,
|
|
577
785
|
running,
|
|
786
|
+
paused,
|
|
578
787
|
replaying,
|
|
579
788
|
start,
|
|
580
789
|
restart,
|
|
790
|
+
pause,
|
|
791
|
+
resumePaused,
|
|
792
|
+
resumeAt,
|
|
581
793
|
next,
|
|
582
794
|
back,
|
|
583
795
|
skip,
|