PageSourceSearch

https://auto-blogger.ai/js/yacoubalhaidari/filament-tour/filament-tour-scripts.js?v=1.0.3.0

js auto-blogger.ai collected 2026-09-25 23:09:48 UTC 15,273 bytes, 526 lines download raw bytes

1// Shepherd is loaded globally from the CDN script in the Blade view
2const Shepherd = window.Shepherd;
3
4let activeTourInstance = null;
5
6/**
7 * Tag sidebar items with data-tour (by URL path, then by navigation label).
8 */
9function applyTourNavAttributes() {
10    const navigationMap = window.navigationMap || {};
11    const dynamicSteps = window.dynamicTourSteps || [];
12
13    // 1) Match by resource/page URL (most reliable with Livewire SPA)
14    dynamicSteps.forEach((step) => {
15        if (!step.id || !step.url) {
16            return;
17        }
18
19        let stepPath;
20
21        try {
22            stepPath = new URL(step.url, window.location.origin).pathname;
23        } catch (e) {
24            return;
25        }
26
27        document.querySelectorAll('a.fi-sidebar-item-btn[href], .fi-sidebar-item a[href]').forEach((link) => {
28            let linkPath;
29
30            try {
31                linkPath = new URL(link.href, window.location.origin).pathname;
32            } catch (e) {
33                return;
34            }
35
36            if (linkPath !== stepPath) {
37                return;
38            }
39
40            const item = link.closest('.fi-sidebar-item') || link;
41
42            item.setAttribute('data-tour', step.id);
43            link.setAttribute('data-tour', step.id);
44        });
45    });
46
47    // 2) Fallback: match sidebar label text
48    document.querySelectorAll('.fi-sidebar-item, .fi-sidebar-group').forEach((item) => {
49        const text = item.textContent.trim();
50        const link = item.querySelector('a.fi-sidebar-item-btn, a[href]') || item;
51
52        Object.entries(navigationMap).forEach(([stepId, navLabel]) => {
53            if (text.includes(navLabel) || text === navLabel) {
54                const sidebarItem = link.closest('.fi-sidebar-item') || item;
55
56                sidebarItem.setAttribute('data-tour', stepId);
57                link.setAttribute('data-tour', stepId);
58            }
59        });
60    });
61}
62
63/** @deprecated alias */
64function autoDetectNavigationElements() {
65    applyTourNavAttributes();
66}
67
68/**
69 * Resolve the sidebar DOM node to highlight for a step.
70 */
71function resolveTourTargetElement(stepId, attachToSelector = null) {
72    applyTourNavAttributes();
73
74    const selector = attachToSelector || `[data-tour="${stepId}"]`;
75    const el = document.querySelector(selector);
76
77    if (!el) {
78        return null;
79    }
80
81    return (
82        el.closest('.fi-sidebar-item') ||
83        el.closest('.fi-sidebar-group-item') ||
84        el.closest('.fi-sidebar-item-button') ||
85        el.closest('li') ||
86        el
87    );
88}
89
90function waitForTourTarget(stepId, attachToSelector = null, maxAttempts = 50, intervalMs = 100) {
91    return new Promise((resolve) => {
92        let attempts = 0;
93
94        const tick = () => {
95            const target = resolveTourTargetElement(stepId, attachToSelector);
96
97            if (target) {
98                resolve(target);
99                return;
100            }
101
102            attempts += 1;
103
104            if (attempts >= maxAttempts) {
105                console.warn(`Tour target not found for step "${stepId}" after ${maxAttempts} attempts`);
106                resolve(null);
107                return;
108            }
109
110            setTimeout(tick, intervalMs);
111        };
112
113        tick();
114    });
115}
116
117/**
118 * Re-apply Shepherd modal cutout and target classes after DOM updates.
119 */
120function refreshShepherdStepTarget(step) {
121    if (!step?.options?.attachTo || !step.tour?.modal) {
122        return false;
123    }
124
125    const attachOn = step.options.attachTo.on || 'right';
126    const selector = `[data-tour="${step.id}"]`;
127    const target = resolveTourTargetElement(step.id, selector);
128
129    if (!target) {
130        return false;
131    }
132
133    if (step.target && step.target !== target) {
134        step._updateStepTargetOnHide();
135    }
136
137    step._resolvedAttachTo = null;
138    step.options.attachTo = {
139        element: target,
140        on: attachOn,
141    };
142    step._resolveAttachToOptions();
143    step.target = target;
144
145    if (step.tour.options.useModalOverlay) {
146        step.tour.modal.setupForStep(step);
147        if (typeof step.tour.modal.show === 'function') {
148            step.tour.modal.show();
149        }
150    }
151
152    step._styleTargetElementForStep(step);
153
154    return true;
155}
156
157function scheduleStepTargetRefresh(step) {
158    if (!step?.options?.attachTo) {
159        return;
160    }
161
162    const run = () => refreshShepherdStepTarget(step);
163
164    run();
165    requestAnimationFrame(() => {
166        run();
167        requestAnimationFrame(run);
168    });
169
170    [100, 350, 700, 1200].forEach((delay) => setTimeout(run, delay));
171}
172
173function highlightNavForStep(stepId) {
174    document.querySelectorAll('.shepherd-tour-active-nav').forEach((item) => {
175        item.classList.remove('shepherd-tour-active-nav');
176    });
177
178    const navItem = document.querySelector(`[data-tour="${stepId}"]`);
179
180    if (!navItem) {
181        return;
182    }
183
184    const navContainer =
185        navItem.closest('.fi-sidebar-item') ||
186        navItem.closest('.fi-sidebar-group-item') ||
187        navItem.closest('.fi-sidebar-item-button') ||
188        navItem.closest('li') ||
189        navItem;
190
191    navContainer.classList.add('shepherd-tour-active-nav');
192
193    if (navItem !== navContainer) {
194        navItem.classList.add('shepherd-tour-active-nav');
195    }
196
197    navContainer.scrollIntoView({
198        behavior: 'smooth',
199        block: 'nearest',
200        inline: 'nearest',
201    });
202}
203
204function waitAfterNavigation() {
205    return new Promise((resolve) => {
206        requestAnimationFrame(() => {
207            requestAnimationFrame(() => {
208                applyTourNavAttributes();
209                setTimeout(resolve, 200);
210            });
211        });
212    });
213}
214
215/**
216 * Full cleanup when the tour ends (complete / cancel).
217 */
218function cleanupTourState() {
219    document.querySelectorAll('.shepherd-tour-active-nav').forEach((item) => {
220        item.classList.remove('shepherd-tour-active-nav');
221    });
222
223    document.querySelectorAll('.tour-original-active').forEach((item) => {
224        item.classList.remove('tour-original-active');
225    });
226
227    document.querySelectorAll('.shepherd-target, .shepherd-enabled').forEach((el) => {
228        el.classList.remove(
229            'shepherd-target',
230            'shepherd-enabled',
231            'shepherd-target-click-disabled',
232        );
233    });
234
235    document.querySelectorAll('.shepherd-modal-overlay-container').forEach((el) => {
236        el.remove();
237    });
238
239    activeTourInstance = null;
240    syncSidebarActiveState();
241}
242
243/**
244 * Mark only the sidebar item matching the current URL as active.
245 */
246function syncSidebarActiveState() {
247    const currentPath = window.location.pathname;
248
249    document.querySelectorAll('.fi-sidebar-item.fi-active, .fi-sidebar-group-item.fi-active').forEach((item) => {
250        item.classList.remove('fi-active');
251    });
252
253    document.querySelectorAll('a.fi-sidebar-item-btn[href]').forEach((link) => {
254        let linkPath;
255
256        try {
257            linkPath = new URL(link.href, window.location.origin).pathname;
258        } catch (e) {
259            return;
260        }
261
262        if (linkPath !== currentPath) {
263            return;
264        }
265
266        const item = link.closest('.fi-sidebar-item') || link.closest('.fi-sidebar-group-item');
267
268        if (item) {
269            item.classList.add('fi-active');
270        }
271    });
272}
273
274function hideModalOverlay(step) {
275    if (step?.tour?.modal?.hide) {
276        step.tour.modal.hide();
277    }
278}
279
280function navigateToStepUrl(stepData) {
281    return new Promise((resolve) => {
282        if (!stepData.url) {
283            resolve();
284            return;
285        }
286
287        const currentUrl = window.location.pathname;
288        const targetUrl = new URL(stepData.url, window.location.origin).pathname;
289
290        if (currentUrl === targetUrl) {
291            waitAfterNavigation().then(resolve);
292            return;
293        }
294
295        const finish = () => waitAfterNavigation().then(resolve);
296
297        if (typeof Livewire !== 'undefined' && Livewire.navigate) {
298            document.addEventListener('livewire:navigated', function handler() {
299                document.removeEventListener('livewire:navigated', handler);
300                finish();
301            }, { once: true });
302
303            Livewire.navigate(stepData.url);
304            return;
305        }
306
307        window.location.href = stepData.url;
308    });
309}
310
311export function initializeShepherdTour() {
312    const tour = new Shepherd.Tour({
313        useModalOverlay: true,
314        defaultStepOptions: {
315            classes: 'shepherd-theme-custom',
316            scrollTo: { behavior: 'smooth', block: 'center' },
317            cancelIcon: {
318                enabled: true,
319                label: window.tourTranslations?.buttons?.cancel || 'Close',
320            },
321            modalOverlayOpeningRadius: 12,
322            modalOverlayOpeningPadding: 10,
323        },
324        tourName: 'app-tour',
325    });
326
327    activeTourInstance = tour;
328
329    tour.on('start', () => {
330        applyTourNavAttributes();
331    });
332
333    tour.on('show', (event) => {
334        if (!event.step) {
335            return;
336        }
337
338        localStorage.setItem('shepherd-tour-current-step', event.step.id);
339        localStorage.setItem('shepherd-tour-in-progress', 'true');
340    });
341
342    tour.on('complete', () => {
343        localStorage.removeItem('shepherd-tour-current-step');
344        localStorage.removeItem('shepherd-tour-in-progress');
345        localStorage.setItem('shepherd-tour-completed', 'true');
346        localStorage.setItem('shepherd-tour-completed-at', new Date().toISOString());
347        cleanupTourState();
348    });
349
350    tour.on('cancel', () => {
351        localStorage.removeItem('shepherd-tour-current-step');
352        localStorage.removeItem('shepherd-tour-in-progress');
353        cleanupTourState();
354    });
355
356    const dynamicSteps = window.dynamicTourSteps || [];
357    const welcomeStep = window.customWelcomeStep;
358    const finishStep = window.customFinishStep;
359
360    const allSteps = [];
361    if (welcomeStep) {
362        allSteps.push(welcomeStep);
363    }
364    allSteps.push(...dynamicSteps);
365    if (finishStep) {
366        allSteps.push(finishStep);
367    }
368
369    allSteps.forEach((stepData) => {
370        const stepConfig = {
371            id: stepData.id,
372            title: stepData.title,
373            text: stepData.text,
374        };
375
376        const attachToSelector = stepData.attachTo || null;
377
378        if (stepData.url || attachToSelector) {
379            stepConfig.beforeShowPromise = function () {
380                return navigateToStepUrl(stepData).then(() => {
381                    if (!attachToSelector) {
382                        return null;
383                    }
384
385                    return waitForTourTarget(stepData.id, attachToSelector);
386                });
387            };
388        }
389
390        if (attachToSelector) {
391            stepConfig.attachTo = {
392                element() {
393                    return resolveTourTargetElement(this.id, attachToSelector);
394                },
395                on: stepData.position || 'right',
396            };
397
398            stepConfig.when = {
399                show() {
400                    applyTourNavAttributes();
401                    highlightNavForStep(this.id);
402                    scheduleStepTargetRefresh(this);
403                },
404            };
405        } else {
406            // Welcome / finish: centered tooltip without full-screen dark overlay
407            const existingWhen = stepConfig.when || {};
408
409            stepConfig.when = {
410                ...existingWhen,
411                show() {
412                    if (typeof existingWhen.show === 'function') {
413                        existingWhen.show.call(this);
414                    }
415
416                    hideModalOverlay(this);
417                },
418            };
419        }
420
421        const buttons = [];
422        const stepButtons = stepData.buttons || [
423            { text: window.tourTranslations?.buttons?.previous || 'Previous', action: 'back', secondary: true },
424            { text: window.tourTranslations?.buttons?.next || 'Next', action: 'next', secondary: false },
425        ];
426
427        stepButtons.forEach((btnData) => {
428            const button = {
429                text: btnData.text,
430                secondary: btnData.secondary || false,
431            };
432
433            if (btnData.action === 'back') {
434                button.action = tour.back;
435            } else if (btnData.action === 'next') {
436                button.action = tour.next;
437            } else if (btnData.action === 'cancel') {
438                button.action = tour.cancel;
439            } else if (btnData.action === 'complete') {
440                button.action = tour.complete;
441            }
442
443            buttons.push(button);
444        });
445
446        stepConfig.buttons = buttons;
447        tour.addStep(stepConfig);
448    });
449
450    return tour;
451}
452
453document.addEventListener('DOMContentLoaded', function () {
454    applyTourNavAttributes();
455    setTimeout(applyTourNavAttributes, 1000);
456
457    document.addEventListener('livewire:navigated', () => {
458        setTimeout(() => {
459            applyTourNavAttributes();
460
461            const currentStep = activeTourInstance?.getCurrentStep?.();
462
463            if (currentStep) {
464                highlightNavForStep(currentStep.id);
465                scheduleStepTargetRefresh(currentStep);
466            }
467        }, 100);
468    });
469
470    const observer = new MutationObserver(function (mutations) {
471        mutations.forEach(function (mutation) {
472            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
473                clearTimeout(window.tourAttributeTimeout);
474                window.tourAttributeTimeout = setTimeout(applyTourNavAttributes, 150);
475            }
476        });
477    });
478
479    const sidebar = document.querySelector('.fi-sidebar');
480
481    if (sidebar) {
482        observer.observe(sidebar, {
483            childList: true,
484            subtree: true,
485        });
486    }
487
488    document.querySelectorAll('[data-shepherd-tour-trigger]').forEach((button) => {
489        button.addEventListener('click', function (e) {
490            e.preventDefault();
491
492            try {
493                const tour = initializeShepherdTour();
494                const inProgress = localStorage.getItem('shepherd-tour-in-progress');
495                const currentStepId = localStorage.getItem('shepherd-tour-current-step');
496
497                if (inProgress === 'true' && currentStepId) {
498                    tour.show(currentStepId);
499                } else {
500                    tour.start();
501                }
502            } catch (error) {
503                console.error('Error starting tour:', error);
504                alert('An error occurred while starting the tour. Please try again.');
505            }
506        });
507    });
508
509    const inProgress = localStorage.getItem('shepherd-tour-in-progress');
510    const currentStepId = localStorage.getItem('shepherd-tour-current-step');
511
512    if (inProgress === 'true' && currentStepId) {
513        setTimeout(() => {
514            try {
515                const tour = initializeShepherdTour();
516                tour.show(currentStepId);
517            } catch (error) {
518                console.error('Error auto-resuming tour:', error);
519                localStorage.removeItem('shepherd-tour-in-progress');
520                localStorage.removeItem('shepherd-tour-current-step');
521            }
522        }, 1500);
523    }
524});
525
526export default initializeShepherdTour;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.