PageSourceSearch

https://healthylifenetwork.com.au/assets/controllers/docs-sync-writer_controller-rOHKwtT.js

js healthylifenetwork.com.au collected 2026-10-02 16:02:12 UTC 5,005 bytes, 128 lines download raw bytes

1import { Controller } from '@hotwired/stimulus';
2
3/**
4 * docs-sync-writer
5 *
6 * Drives the "Generate" button on each drift-finding card in
7 * /admin/developer/docs-sync.  One controller wraps the entire findings list;
8 * individual Generate buttons pass the finding index via data attributes.
9 *
10 * Targets:  modal, draftTitle, draftContent, saveBtn
11 * Values:   generateUrl (String), writeUrl (String), csrfToken (String), days (Number)
12 */
13export default class extends Controller {
14    static targets = ['modal', 'draftTitle', 'draftContent', 'saveBtn'];
15    static values  = {
16        generateUrl: String,
17        writeUrl:    String,
18        csrfToken:   String,
19        days:        Number,
20    };
21
22    // Retained between generate → save
23    #currentPath  = '';
24    #currentDraft = '';
25
26    // ── Generate ────────────────────────────────────────────────────────────
27
28    async generate(event) {
29        const btn          = event.currentTarget;
30        const findingIndex = btn.dataset.findingIndex;
31        const moduleName   = btn.dataset.module;
32        const playbookPath = btn.dataset.playbookPath;
33
34        btn.disabled     = true;
35        btn.dataset.orig = btn.innerHTML;
36        btn.innerHTML    = '<span class="loading loading-spinner loading-xs"></span> Generating…';
37
38        try {
39            const resp = await fetch(this.generateUrlValue, {
40                method:  'POST',
41                headers: {
42                    'Content-Type':     'application/json',
43                    'X-Requested-With': 'XMLHttpRequest',
44                },
45                body: JSON.stringify({ finding_index: Number(findingIndex), days: this.daysValue }),
46            });
47
48            const data = await resp.json();
49
50            if (!resp.ok) {
51                this.#showDraft(`Error: ${data.error ?? 'Generation failed'}`, moduleName, playbookPath, true);
52                return;
53            }
54
55            this.#currentDraft = data.draft;
56            this.#currentPath  = data.playbook_path ?? playbookPath;
57            this.#showDraft(data.draft, moduleName, this.#currentPath, false);
58
59        } catch (err) {
60            this.#showDraft(`Network error: ${err.message}`, moduleName, playbookPath, true);
61        } finally {
62            btn.disabled  = false;
63            btn.innerHTML = btn.dataset.orig;
64        }
65    }
66
67    // ── Save ─────────────────────────────────────────────────────────────────
68
69    async save() {
70        const btn = this.saveBtnTarget;
71        btn.disabled  = true;
72        btn.innerHTML = '<span class="loading loading-spinner loading-xs"></span> Saving…';
73
74        try {
75            const resp = await fetch(this.writeUrlValue, {
76                method:  'POST',
77                headers: {
78                    'Content-Type':     'application/json',
79                    'X-Requested-With': 'XMLHttpRequest',
80                },
81                body: JSON.stringify({
82                    playbook_path: this.#currentPath,
83                    content:       this.#currentDraft,
84                    _token:        this.csrfTokenValue,
85                }),
86            });
87
88            const data = await resp.json();
89
90            if (!resp.ok) {
91                btn.innerHTML = data.error ?? 'Error';
92                btn.classList.add('btn-error');
93                btn.disabled = false;
94                return;
95            }
96
97            btn.innerHTML = '✓ Saved';
98            btn.classList.remove('btn-primary');
99            btn.classList.add('btn-success');
100            setTimeout(() => this.modalTarget.close(), 1800);
101
102        } catch (err) {
103            btn.innerHTML = 'Error';
104            btn.classList.add('btn-error');
105            btn.disabled = false;
106        }
107    }
108
109    // ── Close ─────────────────────────────────────────────────────────────────
110
111    close() {
112        this.modalTarget.close();
113    }
114
115    // ── Private ───────────────────────────────────────────────────────────────
116
117    #showDraft(draft, moduleName, path, isError) {
118        this.draftTitleTarget.textContent   = isError
119            ? `Error — ${moduleName}`
120            : `Generated Playbook — ${moduleName}`;
121        this.draftContentTarget.textContent = draft;
122        this.saveBtnTarget.disabled         = isError;
123        this.saveBtnTarget.classList.remove('btn-success', 'btn-error');
124        this.saveBtnTarget.classList.add('btn-primary');
125        this.saveBtnTarget.innerHTML = `Save to ${path}`;
126        this.modalTarget.showModal();
127    }
128}

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.