/** * Конструктор шаблонов отчётов (секции + подписи) и Ace для advanced HTML. */ (function () { var ACE_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.2/ace.js'; var editors = {}; var previewTimer = null; var aceReady = false; function csrfToken() { var el = document.querySelector('#report-tpl-form input[name=csrfmiddlewaretoken]'); if (el && el.value) return el.value; var m = document.cookie.match(/csrftoken=([^;]+)/); return m ? decodeURIComponent(m[1]) : ''; } function currentMode() { var root = document.getElementById('report-tpl-editor-root'); return (root && root.getAttribute('data-mode')) || 'constructor'; } function setMode(mode) { var root = document.getElementById('report-tpl-editor-root'); if (!root) return; root.setAttribute('data-mode', mode); var engine = document.getElementById('tpl-engine'); if (engine) engine.value = mode === 'advanced' ? 'django_html' : 'blocks_v1'; var c = document.getElementById('tpl-panel-constructor'); var a = document.getElementById('tpl-panel-advanced'); if (c) c.hidden = mode !== 'constructor'; if (a) a.hidden = mode !== 'advanced'; document.querySelectorAll('.tpl-mode-btn').forEach(function (btn) { var on = btn.getAttribute('data-mode') === mode; btn.style.fontWeight = on ? '600' : ''; btn.style.borderBottom = on ? '2px solid var(--color-primary)' : ''; btn.style.borderRadius = on ? '0' : ''; }); if (mode === 'advanced') { ensureAce(function () { mountEditor('tpl-body-html', 'ace/mode/html'); mountEditor('tpl-body-md', 'ace/mode/markdown'); // sync blocks JSON into a visible ace if host exists var blocksTa = document.getElementById('tpl-blocks'); var adv = document.getElementById('tpl-blocks-adv'); if (blocksTa && adv) adv.value = blocksTa.value; mountEditor('tpl-blocks-adv', 'ace/mode/json'); // mirror: when blocks-adv changes, copy to tpl-blocks if (editors['tpl-blocks-adv']) { editors['tpl-blocks-adv'].session.off('change', editors['tpl-blocks-adv']._blocksMirror); editors['tpl-blocks-adv']._blocksMirror = function () { var ta = document.getElementById('tpl-blocks'); if (ta) ta.value = editors['tpl-blocks-adv'].getValue(); schedulePreview(); }; editors['tpl-blocks-adv'].session.on('change', editors['tpl-blocks-adv']._blocksMirror); } }); } else { syncConstructorToBlocks(); } schedulePreview(); } function loadAce(cb) { if (window.ace) { cb(); return; } if (window._aceLoading) { window._aceLoading.push(cb); return; } window._aceLoading = [cb]; var s = document.createElement('script'); s.src = ACE_CDN; s.onload = function () { var q = window._aceLoading || []; window._aceLoading = null; q.forEach(function (fn) { fn(); }); }; s.onerror = function () { window._aceLoading = null; console.error('Не удалось загрузить Ace editor'); }; document.head.appendChild(s); } function ensureAce(cb) { loadAce(function () { aceReady = true; cb(); }); } function syncAceToTextareas() { Object.keys(editors).forEach(function (id) { var ta = document.getElementById(id); if (ta && editors[id]) ta.value = editors[id].getValue(); }); var adv = document.getElementById('tpl-blocks-adv'); var blocks = document.getElementById('tpl-blocks'); if (adv && blocks && currentMode() === 'advanced') { blocks.value = adv.value; } } function mountEditor(textareaId, mode) { var root = document.getElementById('report-tpl-editor-root'); var readOnly = root && root.getAttribute('data-readonly') === '1'; var ta = document.getElementById(textareaId); if (!ta || !window.ace) return; var hostId = textareaId === 'tpl-blocks-adv' ? 'tpl-blocks-ace' : (textareaId + '-ace'); var host = document.getElementById(hostId); if (!host) return; if (editors[textareaId]) { try { editors[textareaId].destroy(); } catch (e) { /* ignore */ } delete editors[textareaId]; } host.innerHTML = ''; host.style.display = 'block'; ta.style.position = 'absolute'; ta.style.left = '-9999px'; ta.style.height = '1px'; ta.style.width = '1px'; ta.setAttribute('aria-hidden', 'true'); var ed = ace.edit(host); ed.setTheme('ace/theme/textmate'); ed.session.setMode(mode); ed.setValue(ta.value || '', -1); ed.setReadOnly(!!readOnly); ed.setOptions({ fontSize: '13px', showPrintMargin: false, wrap: true, useSoftTabs: true, tabSize: 2, }); ed.session.on('change', function () { ta.value = ed.getValue(); if (textareaId === 'tpl-body-html' || textareaId === 'tpl-blocks-adv') { schedulePreview(); } }); editors[textareaId] = ed; } function readBlocksJson() { var ta = document.getElementById('tpl-blocks'); try { return JSON.parse((ta && ta.value) || '{}'); } catch (e) { return { labels: {}, sections: [] }; } } function writeBlocksJson(obj) { var ta = document.getElementById('tpl-blocks'); if (ta) ta.value = JSON.stringify(obj, null, 2); if (editors['tpl-blocks-adv']) { editors['tpl-blocks-adv'].setValue(ta.value, -1); } } function syncConstructorToBlocks() { var data = readBlocksJson(); if (!data.labels || typeof data.labels !== 'object') data.labels = {}; document.querySelectorAll('.tpl-label-input').forEach(function (inp) { var key = inp.getAttribute('data-label-key'); if (key) data.labels[key] = inp.value; }); var sections = []; document.querySelectorAll('#tpl-sections-list .tpl-section-row').forEach(function (row) { var on = row.querySelector('.tpl-section-on'); if (on && on.checked) sections.push(row.getAttribute('data-section-id')); }); data.sections = sections; writeBlocksJson(data); return data; } function moveSection(row, dir) { var list = document.getElementById('tpl-sections-list'); if (!list || !row) return; if (dir < 0 && row.previousElementSibling) { list.insertBefore(row, row.previousElementSibling); } else if (dir > 0 && row.nextElementSibling) { list.insertBefore(row.nextElementSibling, row); } syncConstructorToBlocks(); schedulePreview(); } function schedulePreview() { if (previewTimer) clearTimeout(previewTimer); previewTimer = setTimeout(runPreview, 500); } function runPreview() { var root = document.getElementById('report-tpl-editor-root'); var frame = document.getElementById('report-tpl-preview-frame'); if (!root || !frame) return; var pk = root.getAttribute('data-tpl-id'); if (!pk) return; if (currentMode() === 'constructor') { syncConstructorToBlocks(); } else { syncAceToTextareas(); } var htmlTa = document.getElementById('tpl-body-html'); var blocksTa = document.getElementById('tpl-blocks'); var engineEl = document.getElementById('tpl-engine'); var monthSel = document.getElementById('tpl-preview-month'); var status = document.getElementById('report-tpl-preview-status'); var fd = new FormData(); fd.append('engine', engineEl ? engineEl.value : 'blocks_v1'); fd.append('body_html', htmlTa ? htmlTa.value : ''); fd.append('blocks', blocksTa ? blocksTa.value : '{}'); if (monthSel) fd.append('month', monthSel.value); if (status) status.textContent = 'Обновление…'; fetch('/reports/templates/' + pk + '/preview-draft/', { method: 'POST', headers: { 'X-CSRFToken': csrfToken() }, body: fd, credentials: 'same-origin', }).then(function (res) { return res.text().then(function (text) { return { ok: res.ok, text: text }; }); }).then(function (r) { frame.srcdoc = r.text; if (status) status.textContent = r.ok ? 'Предпросмотр' : 'Ошибка в шаблоне'; }).catch(function () { if (status) status.textContent = 'Сеть / ошибка запроса'; }); } function bindConstructor() { var list = document.getElementById('tpl-sections-list'); if (list && !list._bound) { list._bound = true; list.addEventListener('change', function (e) { if (e.target && e.target.classList.contains('tpl-section-on')) { var row = e.target.closest('.tpl-section-row'); if (row) row.style.opacity = e.target.checked ? '' : '0.55'; syncConstructorToBlocks(); schedulePreview(); } }); list.addEventListener('click', function (e) { var up = e.target.closest('.tpl-sec-up'); var down = e.target.closest('.tpl-sec-down'); if (up) { e.preventDefault(); moveSection(up.closest('.tpl-section-row'), -1); } else if (down) { e.preventDefault(); moveSection(down.closest('.tpl-section-row'), 1); } }); } document.querySelectorAll('.tpl-label-input').forEach(function (inp) { if (inp._bound) return; inp._bound = true; inp.addEventListener('input', function () { syncConstructorToBlocks(); schedulePreview(); }); }); } function init() { var root = document.getElementById('report-tpl-editor-root'); if (!root) return; editors = {}; document.querySelectorAll('.tpl-mode-btn').forEach(function (btn) { if (btn._bound) return; btn._bound = true; btn.addEventListener('click', function () { setMode(btn.getAttribute('data-mode') || 'constructor'); }); }); bindConstructor(); var form = document.getElementById('report-tpl-form'); if (form && !form._tplBound) { form._tplBound = true; form.addEventListener('submit', function () { if (currentMode() === 'constructor') { syncConstructorToBlocks(); } else { syncAceToTextareas(); } }); } var monthSel = document.getElementById('tpl-preview-month'); if (monthSel && !monthSel._tplBound) { monthSel._tplBound = true; monthSel.addEventListener('change', schedulePreview); } var btn = document.getElementById('report-tpl-preview-btn'); if (btn && !btn._tplBound) { btn._tplBound = true; btn.addEventListener('click', function (e) { e.preventDefault(); runPreview(); }); } setMode(currentMode()); } document.addEventListener('DOMContentLoaded', init); document.addEventListener('htmx:afterSwap', function (ev) { var t = ev.detail && ev.detail.target; if (t && (t.id === 'main-content' || (t.querySelector && t.querySelector('#report-tpl-editor-root')))) { init(); } }); })();