PageSourceSearch

https://cadeaubonnen.sheetz.nl/assets/controllers/admin/mail_merge_controller-sdV0Wmn.js

js sheetz.nl collected 2026-10-04 01:38:53 UTC 6,863 bytes, 204 lines download raw bytes

1import { Controller } from '@hotwired/stimulus';
2import Modal from '../../js/modal.js';
3
4/**
5 * Mail Merge Controller
6 * 
7 * Handles insertion of mail merge tags into various input types:
8 * - Regular text inputs
9 * - Textareas
10 * - WYSIWYG editors (via TipTap)
11 * 
12 * Usage:
13 * <div data-controller="admin--mail-merge" 
14 *      data-admin--mail-merge-admin--wysiwyg-outlet="#wysiwyg-wrapper">
15 *   <button data-action="click->admin--mail-merge#insertTag" 
16 *           data-tag="{{customer.firstName}}">
17 *     Insert First Name
18 *   </button>
19 * </div>
20 */
21export default class extends Controller {
22    static outlets = ['admin--wysiwyg'];
23    static targets = ['input'];
24    static values = {
25        targetSelector: String, // Optional: selector for the target input
26        category: String // Optional: category for filtering merge tags (e.g., 'customer', 'gift_card')
27    };
28
29    /**
30     * Store reference to the input target when button is clicked
31     * This ensures we know which input to insert into when using the modal
32     */
33    storeInput(event) {
34        if (this.hasInputTarget) {
35            window._lastFocusedInput = this.inputTarget;
36        }
37    }
38
39    /**
40     * Store reference to the wysiwyg outlet when button is clicked from toolbar
41     */
42    storeWysiwyg(event) {
43        if (this.hasAdminWysiwygOutlet) {
44            window._lastFocusedWysiwyg = this.adminWysiwygOutlet;
45        }
46        // Store the category for filtering tags in the modal
47        if (this.hasCategoryValue) {
48            window._mailMergeCategory = this.categoryValue;
49        } else {
50            window._mailMergeCategory = null;
51        }
52    }
53
54    /**
55     * Insert a mail merge tag
56     * Can be called with data-tag attribute or by passing the tag directly
57     */
58    insertTag(event) {
59        const button = event.currentTarget;
60        const tag = button.dataset.tag || event.detail?.tag;
61        
62        if (!tag) {
63            console.warn('No tag provided for insertion');
64            return;
65        }
66
67        // Check if we have a stored wysiwyg reference (from toolbar button click)
68        if (window._lastFocusedWysiwyg && window._lastFocusedWysiwyg.editor) {
69            this.insertIntoWysiwygEditor(window._lastFocusedWysiwyg.editor, tag);
70            this.closeModal();
71            return;
72        }
73
74        // Check if we have a stored input reference (from React components or buttons)
75        if (window._lastFocusedInput) {
76            this.insertIntoInput(tag);
77        }
78        // Try to insert into WYSIWYG editor if outlet is connected
79        else if (this.hasAdminWysiwygOutlet && this.adminWysiwygOutlet.editor) {
80            this.insertIntoWysiwyg(tag);
81        } 
82        // Fall back to regular input/textarea insertion
83        else {
84            this.insertIntoInput(tag);
85        }
86
87        // Close the modal and clear the stored input reference
88        this.closeModal();
89    }
90
91    /**
92     * Close the modal and clean up
93     */
94    closeModal() {
95        // Clear the stored references
96        window._lastFocusedInput = null;
97        window._lastFocusedWysiwyg = null;
98        window._mailMergeCategory = null;
99        
100        // Close the modal using the imported Modal class
101        const modalElement = document.getElementById('mail_merge_modal');
102        if (modalElement) {
103            Modal.close(modalElement);
104        }
105    }
106
107    /**
108     * Insert tag into TipTap WYSIWYG editor
109     */
110    insertIntoWysiwyg(tag) {
111        const editor = this.adminWysiwygOutlet.editor;
112        this.insertIntoWysiwygEditor(editor, tag);
113    }
114
115    /**
116     * Insert tag into a specific TipTap editor instance
117     */
118    insertIntoWysiwygEditor(editor, tag) {
119        
120        if (!editor) {
121            console.warn('WYSIWYG editor not initialized');
122            return;
123        }
124
125        // Insert the tag at current cursor position
126        editor.chain().focus().insertContent(tag).run();
127    }
128
129    /**
130     * Insert tag into regular input or textarea
131     */
132    insertIntoInput(tag) {
133        let input;
134
135        // Check for globally stored last focused input (for React components)
136        if (window._lastFocusedInput) {
137            input = window._lastFocusedInput;
138        }
139        // Try to find input using target
140        else if (this.hasInputTarget) {
141            input = this.inputTarget;
142        } 
143        // Try using targetSelector value
144        else if (this.hasTargetSelectorValue) {
145            input = document.querySelector(this.targetSelectorValue);
146        }
147        // Fall back to finding any input/textarea in the controller element
148        else {
149            input = this.element.querySelector('input[type="text"], input[type="email"], textarea');
150        }
151
152        if (!input) {
153            console.warn('No input element found for tag insertion');
154            return;
155        }
156
157        // Get cursor position
158        const cursorPos = input.selectionStart || input.value.length;
159        const textBefore = input.value.substring(0, cursorPos);
160        const textAfter = input.value.substring(input.selectionEnd || cursorPos);
161        
162        // Insert tag at cursor position
163        input.value = textBefore + tag + textAfter;
164        
165        // Set cursor position after inserted tag
166        const newCursorPos = cursorPos + tag.length;
167        input.setSelectionRange(newCursorPos, newCursorPos);
168        
169        // Focus the input
170        input.focus();
171        
172        // For React inputs, we need to trigger the onChange handler manually
173        // React 16+ stores event handlers in a special property
174        const reactKey = Object.keys(input).find(key => key.startsWith('__reactProps$'));
175        if (reactKey && input[reactKey]?.onChange) {
176            input[reactKey].onChange({ target: input });
177        } else {
178            // Fallback: Try native value setter trick
179            const prototype = input instanceof HTMLTextAreaElement ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
180            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
181            nativeInputValueSetter.call(input, input.value);
182            
183            // Dispatch input event
184            const inputEvent = new Event('input', { bubbles: true });
185            input.dispatchEvent(inputEvent);
186        }
187    }
188
189    /**
190     * Insert tag using custom event (alternative API)
191     * Usage: this.element.dispatchEvent(new CustomEvent('mail-merge:insert', { detail: { tag: '{{value}}' }}))
192     */
193    connect() {
194        this.element.addEventListener('mail-merge:insert', (event) => {
195            const tag = event.detail?.tag;
196            if (tag) {
197                this.insertTag({ 
198                    currentTarget: this.element,
199                    detail: { tag }
200                });
201            }
202        });
203    }
204}

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.