/* ============================================================ NexusYazı — iskelet (client-side kelime islemcisi) - zengin metin: document.execCommand ile biçim komutları - localStorage: otomatik kayıt (3sn) + aç/kaydet/yeni - dışa aktar: HTML / TXT indir - canlı kelime-karakter sayacı, kısayollar, açık/koyu tema ============================================================ */ (function () { 'use strict'; /* ---------- ELEMANLAR ---------- */ const $ = (s) => document.querySelector(s); const editor = $('#editor'); const fileNameEl = $('#fileName'); const btnTheme = $('#btnTheme'); const btnNew = $('#btnNew'); const btnSave = $('#btnSave'); const btnOpen = $('#btnOpen'); const btnExport = $('#btnExport'); const btnUndo = $('#btnUndo'); const btnRedo = $('#btnRedo'); const btnClearFmt = $('#btnClearFmt'); // --- İçindekiler (başlık gezinme) --- const btnToc = $('#btnToc'); const tocPanel = $('#tocPanel'); const tocBody = $('#tocBody'); const tocEmpty = $('#tocEmpty'); // --- Bul & Değiştir paneli --- const findbar = $('#findbar'); const findInput = $('#findInput'); const replaceInput = $('#replaceInput'); const findCount = $('#findCount'); const btnFindPrev = $('#findPrev'); const btnFindNext = $('#findNext'); const btnReplace = $('#btnReplace'); const btnReplaceAll = $('#btnReplaceAll'); const btnFindClose = $('#findClose'); const selFont = $('#selFont'); const selSize = $('#selSize'); const selBlock = $('#selBlock'); const colorFg = $('#colorFg'); const wrapFg = $('#wrapFg'); const colorBg = $('#colorBg'); const wrapBg = $('#wrapBg'); const saveState = $('#saveState'); const countWords = $('#countWords'); const countChars = $('#countChars'); /* ---------- DURUM ---------- */ const LS_DOC = 'nexusyazi.documento'; const LS_META = 'nexusyazi.meta'; const LS_THEME = 'nexusyazi.tema'; let theme = localStorage.getItem(LS_THEME) || 'light'; let docName = 'Belgesiz.docx'; let dirty = false; let autoSaveTimer = null; /* ========================================================= TEMA ========================================================= */ function applyTheme(t) { theme = t; document.documentElement.setAttribute('data-theme', t); try { localStorage.setItem(LS_THEME, t); } catch (e) {} } btnTheme.addEventListener('click', () => applyTheme(theme === 'dark' ? 'light' : 'dark')); /* ========================================================= ZENGİN METİN KOMUTLARI ========================================================= */ function focusEditor() { try { editor.focus(); } catch (e) {} } function exec(cmd, val) { editor.focus(); const ok = document.execCommand(cmd, false, val); editor.focus(); refreshState(); return ok; } // araç çubuğundaki data-cmd butonları document.querySelectorAll('.tool[data-cmd]').forEach((btn) => { btn.addEventListener('mousedown', (e) => e.preventDefault()); // seçimi koru btn.addEventListener('click', () => { exec(btn.dataset.cmd); }); }); // üst şerit undo/redo btnUndo.addEventListener('mousedown', (e) => e.preventDefault()); btnUndo.addEventListener('click', () => exec('undo')); btnRedo.addEventListener('mousedown', (e) => e.preventDefault()); btnRedo.addEventListener('click', () => exec('redo')); btnClearFmt.addEventListener('mousedown', (e) => e.preventDefault()); btnClearFmt.addEventListener('click', () => exec('removeFormat')); // renkler colorFg.addEventListener('input', (e) => exec('foreColor', e.target.value)); wrapFg.addEventListener('mousedown', (e) => e.preventDefault()); colorBg.addEventListener('input', (e) => exec('hiliteColor', e.target.value)); wrapBg.addEventListener('mousedown', (e) => e.preventDefault()); // font ailesi / boyutu selFont.addEventListener('change', (e) => exec('fontName', e.target.value)); selSize.addEventListener('change', (e) => exec('fontSize', e.target.value)); // blok / başlık stili selBlock.addEventListener('change', (e) => { const v = e.target.value; const after = () => { setBlockActive(v); }; if (v === 'p') { exec('formatBlock', '

'); } else { exec('formatBlock', '<' + v + '>'); } setBlockActive(v); }); /* ========================================================= AKTİF BUTON TAKİBİ (seçime göre vurgula) ========================================================= */ const TOOLS = ['bold', 'italic', 'underline', 'strikeThrough', 'insertUnorderedList', 'insertOrderedList', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull']; function refreshState() { let sel = null; try { sel = window.getSelection(); } catch (e) {} if (sel && sel.rangeCount) { const node = sel.getRangeAt(0).commonAncestorContainer; const el = node.nodeType === 3 ? node.parentElement : node; if (el) { TOOLS.forEach((cmd) => { const b = document.querySelector('.tool[data-cmd="' + cmd + '"]'); if (b) b.classList.toggle('on', !!document.queryCommandState(cmd)); }); setBlockActive(detectBlock(el)); } } // word count live updateCounts(); // undo/redo durumu btnUndo.disabled = !canUndo(); btnRedo.disabled = !canRedo(); } function setBlockActive(tag) { const map = { H1: 'h1', H2: 'h2', H3: 'h3', P: 'p', DIV: 'p', LI: 'p' }; let cur = map[tag] || 'p'; if (tag === 'P') cur = 'p'; if (tag === 'LI') cur = 'p'; selBlock.value = cur; } function detectBlock(el) { let n = el; while (n && n !== editor) { if (n.tagName === 'H1' || n.tagName === 'H2' || n.tagName === 'H3' || n.tagName === 'P') { return n.tagName; } n = n.parentElement; } return 'P'; } function canUndo() { try { return document.queryCommandEnabled('undo'); } catch (e) { return true; } } function canRedo() { try { return document.queryCommandEnabled('redo'); } catch (e) { return true; } } /* ========================================================= KELİME / KARAKTER SAYACI ========================================================= */ function textOf(root) { const clone = root.cloneNode(true); clone.querySelectorAll('script,style').forEach((x) => x.remove()); return (clone.textContent || '').replace(/\u00a0/g, ' '); } function updateCounts() { const raw = textOf(editor); const chars = raw.length; const words = raw.trim() ? raw.trim().split(/\s+/).length : 0; countChars.textContent = chars; countWords.textContent = words; } /* ========================================================= DİRTY / OTOMATİK KAYIT (3 sn) ========================================================= */ function markDirty() { dirty = true; saveState.className = 'save-ind dirty'; saveState.innerHTML = 'Kaydedilmedi'; scheduleAutoSave(); } function scheduleAutoSave() { if (autoSaveTimer) clearTimeout(autoSaveTimer); autoSaveTimer = setTimeout(save, 3000); } /* ========================================================= KAYDET / AÇ / YENİ / DIŞA AKTAR (localStorage) ========================================================= */ function ls() { try { return localStorage; } catch (e) { return null; } } function getMeta() { try { const m = JSON.parse(localStorage.getItem(LS_META) || '{}'); return m; } catch (e) { return {}; } } function setMeta(patch) { const m = Object.assign(getMeta(), patch); try { localStorage.setItem(LS_META, JSON.stringify(m)); } catch (e) {} } function save() { if (!ls()) { setSaveText('Kayıt engellendi'); return; } const html = editor.innerHTML; try { localStorage.setItem(LS_DOC, html); setMeta({ name: docName, savedAt: new Date().toISOString() }); dirty = false; setSaveText('Kaydedildi • ' + new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit', second: '2-digit' })); if (autoSaveTimer) { clearTimeout(autoSaveTimer); autoSaveTimer = null; } } catch (e) { setSaveText('Kayıt hatası'); } } const INFO_WORDS = ['Açıldı', 'İndirildi', 'Hazır', 'yok']; var savingTextTimer = null; function setSaveText(txt) { let cls = 'saving'; if (txt.indexOf('Kaydedildi') !== -1 || INFO_WORDS.some((w) => txt.indexOf(w) !== -1)) { cls = 'saved'; } else if (txt.indexOf('engellendi') !== -1 || txt.indexOf('hata') !== -1) { cls = 'dirty'; } saveState.className = 'save-ind ' + cls; saveState.innerHTML = '' + txt; } function load() { if (!ls()) return; const html = localStorage.getItem(LS_DOC); if (html) { editor.innerHTML = html; // için kirli sayma: temiz başlat dirty = false; const meta = getMeta(); if (meta.name) { docName = meta.name; fileNameEl.textContent = docName; } } else { editor.innerHTML = '

Yeni bir belge... (yazmaya başlayın)

'; } editor.focus(); setCaretToEnd(); refreshState(); } function setCaretToEnd() { try { const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } catch (e) {} } /* Yeni belge */ function neu() { const ok = confirm('Yeni bir belge oluşturulsun mu?\nMevcut belge otomatik kaydedilecek.'); if (!ok) return; save(); docName = 'Belgesiz.docx'; fileNameEl.textContent = docName; editor.innerHTML = '

nexusyazi

Yeni belgenize yazmaya başlayın.

'; setMeta({ name: docName }); editor.focus(); refreshState(); } /* Aç: kayıtlı belgeleri listele */ function openDialog() { if (!ls()) return setSaveText('Aç engellendi'); // Tek-belge modeli: son taslak otomatik yüklendi. Kutu olarak "son kayıt" içeriği döndürülür. // Not: proje tek taslak dosyası tutar (belge + ad). Basit & gizlilik dostu. const cur = localStorage.getItem(LS_DOC); if (!cur) { setSaveText('Kayıtlı belge yok'); return; } const meta = getMeta(); const t = meta.savedAt ? new Date(meta.savedAt).toLocaleString('tr-TR') : 'bilinmiyor'; const go = confirm('Son kaydedilen belge yüklensin mi?\nAd: "' + (meta.name || 'Belgesiz') + '"\nSon kayıt: ' + t); if (go) { editor.innerHTML = cur; fileNameEl.textContent = meta.name || 'Belgesiz.docx'; docName = meta.name || 'Belgesiz.docx'; dirty = false; setSaveText('Açıldı'); } } /* Dışa aktar: HTML ve TXT */ function download(name, content, mime) { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = name; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 300); } function exportDialog() { const fmt = confirm('Nasıl indirmek istersiniz?\n"Tamam" = HTML, "İptal" = TXT (düz metin)'); const base = (docName || 'belge').replace(/\.(docx?|html|txt)$/i, ''); if (fmt) { const full = '' + (docName || 'Belge') + '' + editor.innerHTML + ''; download(base + '.html', full, 'text/html;charset=utf-8'); } else { const plain = editor.innerText || (editor.textContent || ''); download(base + '.txt', plain, 'text/plain;charset=utf-8'); } setSaveText('İndirildi'); } /* ========================================================= ZORUNLU KAYIT İÇİN UNSAVED (Ctrl+S) ========================================================= */ function requestSave() { save(); } /* ========================================================= OLAYLAR ========================================================= */ btnNew.addEventListener('click', neu); btnSave.addEventListener('mousedown', (e) => e.preventDefault()); btnSave.addEventListener('click', requestSave); btnOpen.addEventListener('click', openDialog); btnExport.addEventListener('click', exportDialog); /* --- Dosya adını düzenleme (Enter / odak kaybı / Esc) --- */ function commitFileName() { let name = fileNameEl.textContent.trim().replace(/\s+/g, ' '); if (!name) { name = 'Belgesiz.docx'; } // uzantı yoksa .docx ekle if (!/\.(docx?|html|txt)$/i.test(name)) name += '.docx'; docName = name; fileNameEl.textContent = name; dirty = true; // ad değişimi de kopyalanmalı → otomatik kayıt scheduleAutoSave(); } fileNameEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); fileNameEl.blur(); } else if (e.key === 'Escape') { e.preventDefault(); fileNameEl.textContent = docName; fileNameEl.blur(); } e.stopPropagation(); // editor kısayollarına taşmasın }); fileNameEl.addEventListener('blur', commitFileName); fileNameEl.addEventListener('input', () => { dirty = false; }); // yazarken 'kirlendi' balonu yanmasın; bitince blur commit eder /* ============ BUL & DEĞİŞTİR (native window.find tabanlı — sağlam) ============ */ function findCountTotal(q) { // Tüm eşleşmeleri say: seçimi başa al, native bul'u döngüle try { const sel = window.getSelection(); sel.removeAllRanges(); const r = document.createRange(); r.selectNodeContents(editor); sel.addRange(r); sel.collapseToStart(); let c = 0; while (window.find(q, false, false, false)) { c++; } return c; } catch (e) { return -1; } } function gotoNext(backward) { const q = findInput.value; if (!q) { findCount.textContent = ''; editor.focus(); return; } // native bul: hiç bulamazsa başa dönüp bir kez daha dener if (!window.find(q, false, backward, false) && !window.find(q, false, backward, false)) { findCount.textContent = 'Eşleşme yok'; } else { findCount.textContent = ''; } } function openFind(replaceMode) { findbar.hidden = false; findInput.focus(); findInput.select(); const rs = document.getElementById('find-sep-repl'); const rc = document.getElementById('replaceInput'); const ra = document.getElementById('btnReplaceAll'); // Değiştir alanına ait unsurlar (ayraç + giriş + buton) yalnız Ctrl+H modunda görünür if (rs) rs.style.display = replaceMode ? '' : 'none'; if (rc) rc.style.display = replaceMode ? '' : 'none'; if (ra) ra.style.display = replaceMode ? '' : 'none'; } function closeFind() { findbar.hidden = true; findCount.textContent = ''; editor.focus(); } findInput.addEventListener('input', () => { findCount.textContent = ''; }); btnFindNext.addEventListener('click', () => gotoNext(false)); btnFindPrev.addEventListener('click', () => gotoNext(true)); findInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); gotoNext(e.shiftKey); } if (e.key === 'Escape') { closeFind(); } }); btnFindClose.addEventListener('click', closeFind); btnReplace.addEventListener('click', () => { const q = findInput.value, rep = replaceInput.value; const sel = window.getSelection(); if (!q || !sel.rangeCount) return; // Teğet eşleşme seçili değilse önce bul if (!sel.toString().toLowerCase() || !window.find(q, false, false, false)) { // bulamadıysa ilk geçiş } const s = window.getSelection(); if (s.rangeCount && s.toString()) { s.getRangeAt(0).deleteContents(); s.getRangeAt(0).insertNode(document.createTextNode(rep)); dirty = true; scheduleAutoSave(); updateCounts(); saveState.textContent = '1 kez değiştirildi'; } else { saveState.textContent = 'Önce bir eşleşme seçin (▼ göz at)'; } }); btnReplaceAll.addEventListener('click', () => { const q = findInput.value, rep = replaceInput.value; if (!q) return; // Başa dön ve toplamda kaç, sonra hepsini değiştir const sel = window.getSelection(); const r = document.createRange(); r.selectNodeContents(editor); sel.removeAllRanges(); sel.addRange(r); sel.collapseToStart(); let count = 0, guard = 0; while (window.find(q, false, false, false) && guard++ < 2000) { const s = window.getSelection(); if (!s.rangeCount || !s.toString()) break; s.getRangeAt(0).deleteContents(); s.getRangeAt(0).insertNode(document.createTextNode(rep)); count++; } if (count) { dirty = true; scheduleAutoSave(); updateCounts(); saveState.textContent = count + ' kez değiştirildi'; } findCount.textContent = count ? ('Tümü: ' + count) : 'Eşleşme yok'; }); document.addEventListener('keydown', (e) => { const mod = e.ctrlKey || e.metaKey; if (mod && e.key.toLowerCase() === 'f' && !e.shiftKey) { e.preventDefault(); openFind(false); } else if (mod && e.key.toLowerCase() === 'h') { e.preventDefault(); openFind(true); } }); // düzenleme anında -> dirty + sayaç + araç vurgusu editor.addEventListener('input', markDirty); editor.addEventListener('keyup', refreshState); editor.addEventListener('click', refreshState); editor.addEventListener('mouseup', () => setTimeout(refreshState, 0)); document.addEventListener('selectionchange', () => setTimeout(refreshState, 60)); // klavye kısayolları document.addEventListener('keydown', (e) => { const mod = e.ctrlKey || e.metaKey; if (!mod) return; const k = e.key.toLowerCase(); if (k === 's') { e.preventDefault(); save(); } else if (k === 'b') { e.preventDefault(); exec('bold'); } else if (k === 'i') { e.preventDefault(); exec('italic'); } else if (k === 'u') { e.preventDefault(); exec('underline'); } else if (k === 'z') { e.preventDefault(); exec('undo'); } else if (k === 'y') { e.preventDefault(); exec('redo'); } else if (k === 'a') { /* default select-all */ } }); // pencere kapanırken kirli ise kaydet (best-effort) window.addEventListener('beforeunload', () => { if (dirty) save(); }); /* ========================================================= BAŞLAT ========================================================= */ // Erişilebilirlik: title taşıyan ikon-butonlarına aria-label türet (SVG-only butonlar) function autoAria() { document.querySelectorAll('button.icon-btn[title], button.tool[title]').forEach((b) => { if (!b.getAttribute('aria-label')) { b.setAttribute('aria-label', (b.getAttribute('title') || '').replace(/\s*\(.*\)$/, '').trim()); } }); } function init() { applyTheme(theme); autoAria(); load(); saveState.className = 'save-ind saved'; saveState.innerHTML = 'Hazır'; refreshState(); setupExtras(); bindToc(); } /* ========================================================= GENİŞLETMELER: satır aralığı, girinti, tablo, resim, yazdır ========================================================= */ function parseLH(v) { const n = parseFloat(v); return isNaN(n) ? 0 : n; } // Seçimin "ilgilendirdiği" blokları (p,h1..h3,li,blockquote) sırayla döndürür. function selBlocks() { const s = window.getSelection(); if (!s || s.rangeCount === 0) return []; const r = s.getRangeAt(0); const tagSel = 'p,h1,h2,h3,h4,li,blockquote,figure,table'; function nearest(n) { if (!n) return null; const el = n.nodeType === 1 ? n : (n.parentNode || null); return el && typeof el.closest === 'function' ? el.closest(tagSel) : null; } const first = nearest(r.startContainer); const last = nearest(r.endContainer); if (!first) return []; if (!last || first === last) return [first]; const out = [first]; let cur = first.nextElementSibling; while (cur && cur !== last) { if (cur.matches && cur.matches(tagSel)) out.push(cur); cur = cur.nextElementSibling; } if (last) out.push(last); return out; } // ---- Satır aralığı ---- function setLineHeight(val) { if (!/^\d+(\.\d+)?$/.test(val)) return; selBlocks().forEach(function (b) { b.style.lineHeight = val; }); markDirty(); refreshState(); } // ---- Girinti ---- const INDENT_STEP = 24; function indentBlocks(fn) { selBlocks().forEach(function (b) { if (b.tagName === 'LI' || b.tagName === 'BLOCKQUOTE') return; const cur = parseInt(b.style.paddingLeft, 10) || 0; let v = cur + INDENT_STEP * (fn > 0 ? 1 : -1); if (v < 0) v = 0; if (v > 312) v = 312; b.style.paddingLeft = (v > 0 ? v + 'px' : ''); }); markDirty(); refreshState(); } // ---- Tablo: mini panel + grid ---- const TBL_ROWS = 7, TBL_COLS = 8; var tblHov = { r: 1, c: 1 }; function buildTableGrid() { const g = $('#tblGrid'); g.innerHTML = ''; for (let r = 0; r < TBL_ROWS; r++) { for (let c = 0; c < TBL_COLS; c++) { const cell = document.createElement('div'); cell.className = 'cell'; cell.dataset.r = r + 1; cell.dataset.c = c + 1; cell.addEventListener('mouseenter', function () { g.querySelectorAll('.cell').forEach(function (el) { el.classList.toggle('hov', el.dataset.r <= (r + 1) && el.dataset.c <= (c + 1)); }); tblHov = { r: r + 1, c: c + 1 }; const lb = $('#tblSizeLabel'); if (lb) lb.textContent = (r + 1) + ' × ' + (c + 1); }); cell.addEventListener('click', function () { insertTable(r + 1, c + 1); closeTblPanel(); }); g.appendChild(cell); } } } function insertTable(rows, cols) { let html = ''; for (let r = 0; r < rows; r++) { html += ''; for (let c = 0; c < cols; c++) { const isHead = r === 0; html += (isHead ? ' '; } html += ''; } html += '


'; const ok = exec('insertHTML', html); focusEditor(); markDirty(); refreshState(); } function openTblPanel() { const btn = $('#btnTable'); buildTableGrid(); const panel = $('#tblPanel'); if (!btn || !panel) return; const rect = btn.getBoundingClientRect(); const pRect = panel.getBoundingClientRect(); panel.hidden = false; // Yeniden ölç (fixed, viewport'a göre) const w = panel.offsetWidth; let left = rect.left; if (left + w > window.innerWidth - 8) left = window.innerWidth - w - 8; if (left < 8) left = 8; let top = rect.bottom + 6; if (top + panel.offsetHeight > window.innerHeight - 8) top = rect.top - panel.offsetHeight - 6; panel.style.left = left + 'px'; panel.style.top = top + 'px'; } function closeTblPanel() { const p = $('#tblPanel'); if (p) p.hidden = true; } // ---- Resim ekleme + seçim + yeniden boyutlandırma ---- const inputImg = document.createElement('input'); inputImg.type = 'file'; inputImg.accept = '.png,.jpg,.jpeg,.gif,.bmp,.webp,.svg,image/*'; inputImg.style.display = 'none'; document.body.appendChild(inputImg); inputImg.addEventListener('change', function () { const f = inputImg.files && inputImg.files[0]; inputImg.value = ''; if (!f) return; const fr = new FileReader(); fr.onload = function (ev) { // dosya boyutu güvenliği: 3 MB üstü büyüklükte "önizleme" resmi gömme (gizlilik dostu) const src = ev.target.result; const imgHtml = 'Resim'; exec('insertHTML', imgHtml); focusEditor(); markDirty(); refreshState(); }; fr.onerror = function () { }; fr.readAsDataURL(f); }); // Resim seçilince sarmalayıcı + köşe tutamacı function attachImgHandle(img) { // Zaten sarmalanmış mı? let wrap = img.closest ? img.closest('.img-wrap') : null; if (!wrap) { wrap = document.createElement('span'); wrap.className = 'img-wrap'; wrap.contentEditable = 'false'; img.parentNode.insertBefore(wrap, img); wrap.appendChild(img); } document.querySelectorAll('.img-wrap .img-handle').forEach(function (h) { h.remove(); }); img.classList.add('old-sel'); const handle = document.createElement('span'); handle.className = 'img-handle'; handle.title = 'Sürükleyerek boyutlandır'; wrap.appendChild(handle); let startX, startY, baseW; const onMove = function (e) { e.preventDefault(); let w = Math.max(48, Math.round(baseW + (e.clientX - startX))); const maxW = Math.max(120, editor.getBoundingClientRect().width - 40); if (w > maxW) w = maxW; img.style.width = w + 'px'; img.style.height = 'auto'; }; function onUp() { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor = ''; markDirty(); } handle.addEventListener('mousedown', function (e) { e.preventDefault(); e.stopPropagation(); startX = e.clientX; startY = e.clientY; baseW = img.offsetWidth || 200; document.body.style.cursor = 'nwse-resize'; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }); } function clearImgSel() { document.querySelectorAll('.page .old-sel').forEach(function (i) { i.classList.remove('old-sel'); }); document.querySelectorAll('.img-wrap .img-handle').forEach(function (h) { h.remove(); }); } function handleEditorClick(ev) { const t = ev.target; if (t && t.tagName === 'IMG' && editor.contains(t)) { clearImgSel(); attachImgHandle(t); ev.preventDefault(); } else if (t && t.classList && !t.classList.contains('img-handle') && t.closest && !t.closest('.img-wrap, .tbl-panel')) { clearImgSel(); } } // ---- Yazdır ---- function doPrint() { window.print(); } function setupExtras() { var sel = $('#selLineHeight'); if (sel) { sel.addEventListener('change', function () { setLineHeight(sel.value); }); } const bOut = $('#btnOutdent'), bIn = $('#btnIndent'); if (bOut) bOut.addEventListener('click', function () { indentBlocks(-1); }); if (bIn) bIn .addEventListener('click', function () { indentBlocks(1); }); const bTbl = $('#btnTable'); if (bTbl) bTbl.addEventListener('click', function (e) { const panel = $('#tblPanel'); if (panel && !panel.hidden) { closeTblPanel(); } else { e.stopPropagation(); openTblPanel(); } }); const bImg = $('#btnImage'); if (bImg) bImg.addEventListener('click', function () { inputImg.click(); }); const bPr = $('#btnPrint'); if (bPr) bPr.addEventListener('click', doPrint); if (editor) { editor.addEventListener('click', handleEditorClick); } document.addEventListener('mousedown', function (e) { const panel = $('#tblPanel'); if (panel && !panel.hidden && e.target.closest && !e.target.closest('#btnTable') && !(panel.contains && panel.contains(e.target))) { closeTblPanel(); } }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && $('#tblPanel') && !$('#tblPanel').hidden) { closeTblPanel(); } }); } /* ========================================================= İÇİNDEKİLER (başlık gezinme) ========================================================= */ let tocHideTimer = null; function closeToc() { if (!tocPanel) return; if (btnToc) { btnToc.classList.remove('on'); btnToc.setAttribute('aria-expanded', 'false'); } tocPanel.classList.remove('open'); clearTimeout(tocHideTimer); tocHideTimer = setTimeout(function () { tocPanel.hidden = true; }, 240); } function openToc() { if (!tocPanel) return; clearTimeout(tocHideTimer); tocPanel.hidden = false; if (btnToc) { btnToc.classList.add('on'); btnToc.setAttribute('aria-expanded', 'true'); } // yeniden akışı zorla ki translate geçişi çalışsın void tocPanel.offsetWidth; tocPanel.classList.add('open'); } function toggleToc() { if (!tocPanel) return; if (tocPanel.classList.contains('open')) { closeToc(); } else { openToc(); updateToc(); } } function updateToc() { if (!tocBody || !editor || !tocPanel) return; if (!tocPanel.classList.contains('open')) return; // kapalıyken boşa masraf yok const heads = editor.querySelectorAll('h1, h2, h3'); tocBody.textContent = ''; if (!heads.length) { tocBody.hidden = true; if (tocEmpty) tocEmpty.hidden = false; return; } tocBody.hidden = false; if (tocEmpty) tocEmpty.hidden = true; heads.forEach(function (h) { const txt = (h.textContent || '').trim() || '(Başlıksız)'; const li = document.createElement('li'); const b = document.createElement('button'); b.type = 'button'; b.className = 'toc-item lv-' + h.tagName[1]; // H1→lv-1, H2→lv-2, H3→lv-3 const num = document.createElement('span'); num.className = 'toc-hl'; num.textContent = h.tagName[1]; const sp = document.createElement('span'); sp.className = 'toc-txt'; sp.textContent = txt; b.appendChild(num); b.appendChild(sp); b.addEventListener('click', function () { h.scrollIntoView({ behavior: 'smooth', block: 'start' }); focusEditor(); }); li.appendChild(b); tocBody.appendChild(li); }); } function bindToc() { if (btnToc) btnToc.addEventListener('click', toggleToc); const tc = $('#tocClose'); if (tc) tc.addEventListener('click', closeToc); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && tocPanel && tocPanel.classList.contains('open')) { closeToc(); } }); if (editor) { editor.addEventListener('input', function () { updateToc(); }); editor.addEventListener('keyup', function () { updateToc(); }); } } init(); })();