v1.8.9: fix JS syntax errors — em dash in comment, broken try/catch in saveBranding
This commit is contained in:
+62
-61
@@ -926,15 +926,15 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ADMIN_KEY removed — JWT-only auth
|
||||
// ADMIN_KEY removed - JWT-only auth
|
||||
let JWT_TOKEN = localStorage.getItem('admin_jwt') || '';
|
||||
const RAG_KEY = 'zRf77E_brSEnA2BUi40uxLxT4uofAk661WMZb-cdv4M';
|
||||
let keyVisible = false;
|
||||
|
||||
// Hide login immediately if token present — no flash
|
||||
// Hide login immediately if token present -- no flash
|
||||
if (!JWT_TOKEN) document.getElementById('login').style.display = 'flex';
|
||||
|
||||
// ── Core API ──────────────────────────────────────────────────────────────────
|
||||
// -- Core API ------------------------------------------------------------------
|
||||
async function api(path, opts={}) {
|
||||
const headers = {'Content-Type':'application/json', 'Authorization':'Bearer '+JWT_TOKEN};
|
||||
if (JWT_TOKEN) headers['Authorization'] = 'Bearer ' + JWT_TOKEN;
|
||||
@@ -942,7 +942,7 @@ async function api(path, opts={}) {
|
||||
if (r.status === 401) {
|
||||
JWT_TOKEN = ''; localStorage.removeItem('admin_jwt');
|
||||
document.getElementById('login').style.display = 'flex';
|
||||
throw new Error('Session expired — please login again');
|
||||
throw new Error('Session expired -- please login again');
|
||||
}
|
||||
if (!r.ok) throw new Error(r.status + ' ' + path);
|
||||
return r.json();
|
||||
@@ -961,7 +961,7 @@ function showErr(id, msg) {
|
||||
if (el) el.innerHTML = `<div class="glass border border-red-900/40 rounded-xl p-4 text-red-400 text-sm flex items-center gap-2"><span class="msym">error</span>${msg}</div>`;
|
||||
}
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
// -- Auth ----------------------------------------------------------------------
|
||||
async function doLogin() {
|
||||
const email = document.getElementById('admin-email').value.trim();
|
||||
const pass = document.getElementById('admin-pass').value;
|
||||
@@ -980,7 +980,7 @@ async function doLogin() {
|
||||
} catch(e) { errEl.textContent='Connection error'; errEl.classList.remove('hidden'); }
|
||||
}
|
||||
|
||||
// ── Navigation ────────────────────────────────────────────────────────────────
|
||||
// -- Navigation ----------------------------------------------------------------
|
||||
function showPage(name, btn) {
|
||||
document.querySelectorAll('.page').forEach(p=>p.classList.add('hidden'));
|
||||
document.querySelectorAll('.nav-btn').forEach(b=>{
|
||||
@@ -999,7 +999,7 @@ function showPage(name, btn) {
|
||||
function openSidebar() { document.getElementById('sidebar').classList.add('open'); document.getElementById('sidebar-overlay').classList.add('show'); }
|
||||
function closeSidebar() { document.getElementById('sidebar').classList.remove('open'); document.getElementById('sidebar-overlay').classList.remove('show'); }
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
// -- Helpers -------------------------------------------------------------------
|
||||
function bar(label, val, max, color='#4edea3') {
|
||||
const pct = max>0 ? Math.round(val/max*100) : 0;
|
||||
return `<div class="flex items-center gap-3 mb-3">
|
||||
@@ -1022,7 +1022,7 @@ function badge(llm) {
|
||||
return `<span class="text-[10px] font-bold px-2 py-0.5 rounded-full ${map[llm]||'bg-blue-900/40 text-blue-400'}">${llm}</span>`;
|
||||
}
|
||||
|
||||
// ── Page Loaders ──────────────────────────────────────────────────────────────
|
||||
// -- Page Loaders --------------------------------------------------------------
|
||||
async function loadLibraryHealth() {
|
||||
try {
|
||||
const d = await api('/admin/library-health');
|
||||
@@ -1098,7 +1098,7 @@ async function loadLibraryHealth() {
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
${d.pending_jobs.map(j => `<div class="bg-yellow-900/20 border border-yellow-900/30 rounded-lg px-3 py-2 text-xs flex justify-between">
|
||||
<span class="text-on-surface-variant">${j.status} — ${j.done}/${j.total} done</span>
|
||||
<span class="text-on-surface-variant">${j.status} -- ${j.done}/${j.total} done</span>
|
||||
<span class="text-red-400">${j.errors > 0 ? j.errors + ' errors' : ''}</span>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
@@ -1122,7 +1122,7 @@ async function rebuildBM25() {
|
||||
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm animate-spin">refresh</span> Rebuilding...';
|
||||
try {
|
||||
const r = await api('/admin/rebuild-bm25', {method:'POST'});
|
||||
toast(`BM25 rebuilt — ${r.chunks_indexed} chunks indexed`, 'success');
|
||||
toast(`BM25 rebuilt -- ${r.chunks_indexed} chunks indexed`, 'success');
|
||||
} catch(e) { toast('BM25 rebuild failed: ' + e.message, 'error'); }
|
||||
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">refresh</span> Rebuild BM25';
|
||||
}
|
||||
@@ -1132,7 +1132,7 @@ async function clearCache() {
|
||||
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clearing...';
|
||||
try {
|
||||
const r = await api('/admin/clear-cache', {method:'POST'});
|
||||
toast(`Cache cleared — ${r.cleared} entries removed`, 'success');
|
||||
toast(`Cache cleared -- ${r.cleared} entries removed`, 'success');
|
||||
loadOverview();
|
||||
} catch(e) { toast('Cache clear failed: ' + e.message, 'error'); }
|
||||
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clear Cache';
|
||||
@@ -1359,7 +1359,7 @@ async function loadSettings() {
|
||||
<span class="text-sm text-on-surface-variant">${l}</span><span class="text-xs font-mono glass px-2 py-1 rounded-lg text-primary truncate max-w-[140px]">${v}</span>
|
||||
</div>`).join('')}`;
|
||||
|
||||
// ── R2 / Storage section ──────────────────────────────────────────────────
|
||||
// -- R2 / Storage section --------------------------------------------------
|
||||
document.getElementById('settings-r2').innerHTML = `
|
||||
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-5 flex items-center gap-2">
|
||||
<span class="msym text-base">cloud</span>Cloudflare R2 Storage
|
||||
@@ -1422,8 +1422,8 @@ async function loadSettings() {
|
||||
<p id="r2-status" class="text-xs text-center mt-3 hidden"></p>
|
||||
<div class="mt-5 pt-4 border-t border-outline-variant/20">
|
||||
<p class="text-xs text-on-surface-variant mb-3 font-semibold uppercase tracking-widest">Service Endpoints</p>
|
||||
${[['Typesense',d.typesense_host ? 'http://'+d.typesense_host+':8108' : '—'],
|
||||
['MinIO',d.minio_endpoint||'—'],
|
||||
${[['Typesense',d.typesense_host ? 'http://'+d.typesense_host+':8108' : '--'],
|
||||
['MinIO',d.minio_endpoint||'--'],
|
||||
].map(([l,v])=>
|
||||
'<div class="flex justify-between items-center py-2"><span class="text-xs text-on-surface-variant">'+l+'</span><span class="text-xs font-mono glass px-2 py-1 rounded-lg text-primary">'+v+'</span></div>'
|
||||
).join('')}
|
||||
@@ -1443,7 +1443,7 @@ async function saveR2Config() {
|
||||
R2_PUBLIC_URL: document.getElementById('r2-public').value.trim(),
|
||||
};
|
||||
if (!raw.R2_ENDPOINT) { toast('Endpoint URL مطلوب'); return; }
|
||||
// Only send non-empty values — never overwrite saved keys with blank
|
||||
// Only send non-empty values -- never overwrite saved keys with blank
|
||||
const payload = Object.fromEntries(Object.entries(raw).filter(([,v]) => v !== ''));
|
||||
status.textContent = 'جاري الحفظ...'; status.classList.remove('hidden');
|
||||
try {
|
||||
@@ -1466,10 +1466,10 @@ async function testR2Connection() {
|
||||
const status = document.getElementById('r2-status');
|
||||
status.textContent = 'جاري الاختبار...'; status.classList.remove('hidden');
|
||||
try {
|
||||
// Test-only — never saves, never overwrites stored credentials
|
||||
// Test-only -- never saves, never overwrites stored credentials
|
||||
const r = await api('/admin/r2-test');
|
||||
if (r.ok) {
|
||||
status.textContent = '✅ R2 متصل — ' + (r.detail || '');
|
||||
status.textContent = '✅ R2 متصل -- ' + (r.detail || '');
|
||||
status.style.color = '#4edea3';
|
||||
} else {
|
||||
status.textContent = '❌ ' + (r.detail || 'فشل الاتصال');
|
||||
@@ -1543,12 +1543,13 @@ async function loadBranding() {
|
||||
async function saveBranding() {
|
||||
try {
|
||||
const r = await fetch('/admin/branding', {method:'POST', headers:{'Content-Type':'application/json','Authorization':'Bearer '+JWT_TOKEN}, body:JSON.stringify({site_name:document.getElementById('b-site-name').value, site_subtitle:document.getElementById('b-subtitle').value, logo_url:document.getElementById('b-logo').value, logo_size:document.getElementById('b-logo-size')?.value||'40', primary_color:document.getElementById('b-primary-hex').value, secondary_color:document.getElementById('b-secondary-hex').value})});
|
||||
if (r.ok) toast('Branding saved!','success');
|
||||
const p=document.getElementById('b-primary-hex')?.value;
|
||||
const s=document.getElementById('b-secondary-hex')?.value;
|
||||
if(p) document.documentElement.style.setProperty('--primary',p);
|
||||
if(s) document.documentElement.style.setProperty('--secondary',s);
|
||||
} else alert('Save failed');
|
||||
if (r.ok) {
|
||||
toast('Branding saved!','success');
|
||||
const p=document.getElementById('b-primary-hex')?.value;
|
||||
const s=document.getElementById('b-secondary-hex')?.value;
|
||||
if(p) document.documentElement.style.setProperty('--primary',p);
|
||||
if(s) document.documentElement.style.setProperty('--secondary',s);
|
||||
} else { alert('Save failed'); }
|
||||
} catch(e) { alert('Error: '+e.message); }
|
||||
}
|
||||
|
||||
@@ -1635,7 +1636,7 @@ async function loadClaude() {
|
||||
function toggleApiKey() {
|
||||
const el = document.getElementById('api-key-display'), btn = document.getElementById('key-toggle-btn');
|
||||
keyVisible = !keyVisible;
|
||||
el.textContent = keyVisible ? RAG_KEY : '••••••••••••••••••••••';
|
||||
el.textContent = keyVisible ? RAG_KEY : '**********************';
|
||||
btn.textContent = keyVisible ? 'Hide' : 'Show';
|
||||
}
|
||||
|
||||
@@ -1655,7 +1656,7 @@ async function saveClaudeKey() {
|
||||
} catch(e) { toast('Error: '+e.message); }
|
||||
}
|
||||
|
||||
// ── Auto-login on page load ───────────────────────────────────────────────────
|
||||
// -- Auto-login on page load ---------------------------------------------------
|
||||
if (JWT_TOKEN) {
|
||||
document.getElementById('login').style.display = 'none';
|
||||
fetch('/auth/me', {headers:{'Authorization':'Bearer '+JWT_TOKEN}})
|
||||
@@ -1667,10 +1668,10 @@ if (JWT_TOKEN) {
|
||||
} else {
|
||||
loadOverview();
|
||||
}
|
||||
}).catch(() => { loadOverview(); }); // network error — stay logged in
|
||||
}).catch(() => { loadOverview(); }); // network error -- stay logged in
|
||||
}
|
||||
|
||||
// ── Nav init ──────────────────────────────────────────────────────────────────
|
||||
// -- Nav init ------------------------------------------------------------------
|
||||
document.querySelectorAll('.nav-btn').forEach(b => {
|
||||
if (!b.classList.contains('active')) b.classList.add('text-on-surface-variant','hover:text-primary','hover:bg-primary/5');
|
||||
});
|
||||
@@ -1678,7 +1679,7 @@ document.querySelector('.nav-btn.active')?.classList.add('bg-primary/10','text-p
|
||||
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// TRACKING — Google Analytics 4 + DataLayer Events + Pixel Tracking
|
||||
// TRACKING -- Google Analytics 4 + DataLayer Events + Pixel Tracking
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
let gaEnabled = false;
|
||||
let analyticsEvents = [];
|
||||
@@ -1694,7 +1695,7 @@ const PB_META = {
|
||||
custom: {label:'Custom Code', hint:'', icon:'⚡'},
|
||||
};
|
||||
|
||||
// ── Load all tracking data ────────────────────────────────────────────────────
|
||||
// -- Load all tracking data ----------------------------------------------------
|
||||
async function loadTracking() {
|
||||
try {
|
||||
const [ga, px] = await Promise.all([
|
||||
@@ -1709,7 +1710,7 @@ async function loadTracking() {
|
||||
const statusEl = document.getElementById('ga-status');
|
||||
if (ga.measurement_id) {
|
||||
const on = ga.enabled;
|
||||
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full ' + (on?'bg-emerald-400':'bg-yellow-400') + ' inline-block"></span> <span class="' + (on?'text-emerald-400':'text-yellow-400') + '">' + (on?'Active':'Disabled') + ' — ' + ga.measurement_id + '</span>';
|
||||
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full ' + (on?'bg-emerald-400':'bg-yellow-400') + ' inline-block"></span> <span class="' + (on?'text-emerald-400':'text-yellow-400') + '">' + (on?'Active':'Disabled') + ' -- ' + ga.measurement_id + '</span>';
|
||||
} else {
|
||||
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-outline-variant inline-block"></span> Not configured';
|
||||
}
|
||||
@@ -1720,7 +1721,7 @@ async function loadTracking() {
|
||||
} catch(e) { toast('Tracking: ' + e.message); console.error(e); }
|
||||
}
|
||||
|
||||
// ── GA4 ───────────────────────────────────────────────────────────────────────
|
||||
// -- GA4 -----------------------------------------------------------------------
|
||||
function updateGAToggleBtn(enabled) {
|
||||
const btn = document.getElementById('ga-toggle-btn');
|
||||
if (!btn) return;
|
||||
@@ -1744,7 +1745,7 @@ async function saveGAConfig() {
|
||||
} catch(e) { toast('Save failed: ' + e.message); }
|
||||
}
|
||||
|
||||
// ── DataLayer Events ──────────────────────────────────────────────────────────
|
||||
// -- DataLayer Events ----------------------------------------------------------
|
||||
function renderEventsList() {
|
||||
const el = document.getElementById('analytics-events-list');
|
||||
if (!el) return;
|
||||
@@ -1877,7 +1878,7 @@ async function deleteEvent(idx) {
|
||||
renderEventsList();
|
||||
}
|
||||
|
||||
// ── Pixel Tracking ────────────────────────────────────────────────────────────
|
||||
// -- Pixel Tracking ------------------------------------------------------------
|
||||
function renderPixelsList() {
|
||||
const el = document.getElementById('pixels-list');
|
||||
if (!el) return;
|
||||
@@ -1885,7 +1886,7 @@ function renderPixelsList() {
|
||||
el.innerHTML = trackingPixels.map((px, i) => {
|
||||
const meta = PB_META[px.platform] || PB_META.custom;
|
||||
const on = px.enabled !== false;
|
||||
const label = px.platform === 'custom' ? (px.name || 'Custom') : (px.pixel_id || '—');
|
||||
const label = px.platform === 'custom' ? (px.name || 'Custom') : (px.pixel_id || '--');
|
||||
return '<div class="glass rounded-xl p-4 flex items-center gap-4 ' + (on?'':'opacity-50') + '">' +
|
||||
'<div class="w-10 h-10 rounded-xl bg-purple-900/30 flex items-center justify-center shrink-0 text-xl">' + meta.icon + '</div>' +
|
||||
'<div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-0.5"><span class="text-sm font-bold capitalize">' + px.platform + '</span>' +
|
||||
@@ -1986,7 +1987,7 @@ async function pbDelete(idx) {
|
||||
}
|
||||
|
||||
|
||||
// ── Page Scanner ─────────────────────────────────────────────────────────────
|
||||
// -- Page Scanner -------------------------------------------------------------
|
||||
let scanCurrentPage = null;
|
||||
|
||||
function openScanPicker() {
|
||||
@@ -2084,7 +2085,7 @@ function applyScanResult(item) {
|
||||
updateEBPreview();
|
||||
closeScanPicker();
|
||||
// Toast to confirm
|
||||
toast('Element applied — review and save', 'success');
|
||||
toast('Element applied -- review and save', 'success');
|
||||
}
|
||||
|
||||
|
||||
@@ -2118,9 +2119,9 @@ async function loadLLMConfig() {
|
||||
</div>
|
||||
<div id="llm-claude-fields" class="${d.provider==='claude' ? '' : 'hidden'} space-y-3 mb-4">
|
||||
<div class="glass rounded-xl p-4 text-xs text-on-surface-variant space-y-1 mb-3">
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-purple-400">bolt</span>Anthropic Claude — fast, high quality</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-purple-400">bolt</span>Anthropic Claude -- fast, high quality</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-primary">all_inclusive</span>claude-sonnet-4 recommended for Arabic</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-on-surface-variant">storage</span>API key required — console.anthropic.com</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-on-surface-variant">storage</span>API key required -- console.anthropic.com</p>
|
||||
</div>
|
||||
<div id="claude-key-llm-wrap">
|
||||
<label class="text-xs text-on-surface-variant block mb-1.5">API Key
|
||||
@@ -2153,14 +2154,14 @@ async function loadLLMConfig() {
|
||||
</div>
|
||||
<div id="llm-local-info" class="${isLocal ? '' : 'hidden'} glass rounded-xl p-4 mb-4 text-xs text-on-surface-variant space-y-1">
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-emerald-400">check_circle</span>Running qwen2:7b on your VPS CPU</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-yellow-400">schedule</span>~30-50s per query — streaming masks latency</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-primary">lock</span>Fully private — no external requests</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-yellow-400">schedule</span>~30-50s per query -- streaming masks latency</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-primary">lock</span>Fully private -- no external requests</p>
|
||||
</div>
|
||||
<div id="llm-cf-fields" class="${isCF ? '' : 'hidden'} space-y-3 mb-4">
|
||||
<div class="glass rounded-xl p-4 text-xs text-on-surface-variant space-y-1 mb-3">
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-blue-400">bolt</span>GPU inference — 3-5s per query</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-blue-400">bolt</span>GPU inference -- 3-5s per query</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-primary">all_inclusive</span>~900 free queries/day on free tier</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-on-surface-variant">storage</span>Your PDFs stay local — only text prompt sent</p>
|
||||
<p class="flex items-center gap-2"><span class="msym text-base text-on-surface-variant">storage</span>Your PDFs stay local -- only text prompt sent</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-on-surface-variant block mb-1.5">Account ID</label>
|
||||
@@ -2229,7 +2230,7 @@ async function saveLLMConfig() {
|
||||
status.textContent = 'Saving...'; status.classList.remove('hidden'); status.style.color = '';
|
||||
try {
|
||||
await api('/admin/llm-config', {method:'POST', body:JSON.stringify(payload)});
|
||||
status.textContent = '✅ Saved — restart API container to apply';
|
||||
status.textContent = '✅ Saved -- restart API container to apply';
|
||||
status.style.color = '#4edea3';
|
||||
setTimeout(loadLLMConfig, 1500);
|
||||
} catch(e) { status.textContent = '❌ '+e.message; status.style.color = '#f87171'; }
|
||||
@@ -2261,8 +2262,8 @@ document.querySelectorAll('.spotlight-card').forEach(card => {
|
||||
});
|
||||
|
||||
|
||||
// ── Book Edit ─────────────────────────────────────────────────────────────────
|
||||
let _bookEditCache = {}; // id → book data from last loadBooks()
|
||||
// -- Book Edit -----------------------------------------------------------------
|
||||
let _bookEditCache = {}; // id -> book data from last loadBooks()
|
||||
|
||||
function openBookEdit(btn) {
|
||||
const d = btn.dataset;
|
||||
@@ -2327,7 +2328,7 @@ async function retryMetaExtraction() {
|
||||
status.classList.remove('hidden');
|
||||
try {
|
||||
await api('/admin/books/' + id + '/extract-meta', { method: 'POST' });
|
||||
status.textContent = '✅ طُلب الاستخراج — ستُحدَّث البيانات خلال دقيقة';
|
||||
status.textContent = '✅ طُلب الاستخراج -- ستُحدَّث البيانات خلال دقيقة';
|
||||
status.style.color = '#4edea3';
|
||||
// Reload after 65s
|
||||
setTimeout(() => { closeBookEdit(); loadBooks(); }, 65000);
|
||||
@@ -2337,7 +2338,7 @@ async function retryMetaExtraction() {
|
||||
}
|
||||
}
|
||||
|
||||
// Book data passed via data attributes — no cache needed
|
||||
// Book data passed via data attributes -- no cache needed
|
||||
|
||||
</script>
|
||||
|
||||
@@ -2393,7 +2394,7 @@ async function retryMetaExtraction() {
|
||||
|
||||
|
||||
<script>
|
||||
// ── Page Builder ─────────────────────────────────────────────────────────────
|
||||
// -- Page Builder -------------------------------------------------------------
|
||||
let _pbPage = 'privacy';
|
||||
let _pbMode = 'visual';
|
||||
let _pbBlocks = [];
|
||||
@@ -2406,7 +2407,7 @@ const PB_PAGES = {
|
||||
index: { label: 'Home', url: '/' },
|
||||
};
|
||||
|
||||
// ── Switch page ───────────────────────────────────────────────────────────────
|
||||
// -- Switch page ---------------------------------------------------------------
|
||||
function pbSwitch(page) {
|
||||
_pbPage = page;
|
||||
document.querySelectorAll('.pb-tab').forEach(b => {
|
||||
@@ -2418,7 +2419,7 @@ function pbSwitch(page) {
|
||||
pbLoad();
|
||||
}
|
||||
|
||||
// ── Mode toggle ───────────────────────────────────────────────────────────────
|
||||
// -- Mode toggle ---------------------------------------------------------------
|
||||
function pbSetMode(mode) {
|
||||
_pbMode = mode;
|
||||
const mv = document.getElementById('pb-mode-visual');
|
||||
@@ -2434,12 +2435,12 @@ function pbSetMode(mode) {
|
||||
mh.className = 'px-3 py-1.5 rounded-lg text-xs font-bold bg-primary/20 text-primary border border-primary/30 flex items-center gap-1';
|
||||
mv.className = 'px-3 py-1.5 rounded-lg text-xs font-bold glass text-on-surface-variant flex items-center gap-1';
|
||||
vh.classList.remove('hidden'); vv.classList.add('hidden');
|
||||
// Sync blocks → HTML
|
||||
// Sync blocks -> HTML
|
||||
document.getElementById('pb-html-editor').value = _pbRawHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load page content ─────────────────────────────────────────────────────────
|
||||
// -- Load page content ---------------------------------------------------------
|
||||
async function pbLoad() {
|
||||
const blocksEl = document.getElementById('pb-blocks');
|
||||
blocksEl.innerHTML = '<div class="text-on-surface-variant text-sm p-4">Loading...</div>';
|
||||
@@ -2455,7 +2456,7 @@ async function pbLoad() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Parse HTML → blocks ───────────────────────────────────────────────────────
|
||||
// -- Parse HTML -> blocks -------------------------------------------------------
|
||||
function pbParseHTML(html) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
@@ -2485,11 +2486,11 @@ function pbParseHTML(html) {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
// ── Render blocks in visual editor ───────────────────────────────────────────
|
||||
// -- Render blocks in visual editor -------------------------------------------
|
||||
function pbRenderBlocks() {
|
||||
const container = document.getElementById('pb-blocks');
|
||||
if (!_pbBlocks.length) {
|
||||
container.innerHTML = '<div class="glass rounded-2xl p-6 text-center text-on-surface-variant text-sm">No blocks yet — add one below</div>';
|
||||
container.innerHTML = '<div class="glass rounded-2xl p-6 text-center text-on-surface-variant text-sm">No blocks yet -- add one below</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = _pbBlocks.map((b, idx) => pbBlockHTML(b, idx)).join('');
|
||||
@@ -2555,7 +2556,7 @@ function pbBlockHTML(b, idx) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Block actions ─────────────────────────────────────────────────────────────
|
||||
// -- Block actions -------------------------------------------------------------
|
||||
function pbAddBlock(type) {
|
||||
const newBlock = { id: Date.now(), type, content: '', level: 'h2', ordered: false, title: '', subtitle: '', email: '' };
|
||||
_pbBlocks.push(newBlock);
|
||||
@@ -2579,7 +2580,7 @@ function pbMoveBlock(idx, dir) {
|
||||
pbRenderBlocks();
|
||||
}
|
||||
|
||||
// ── Blocks → HTML ─────────────────────────────────────────────────────────────
|
||||
// -- Blocks -> HTML -------------------------------------------------------------
|
||||
function pbBlocksToHTML(blocks) {
|
||||
return blocks.map(b => {
|
||||
if (b.type === 'heading') return `<${b.level||'h2'} class="text-primary font-bold mb-2">${b.content}</${b.level||'h2'}>`;
|
||||
@@ -2597,7 +2598,7 @@ function pbBlocksToHTML(blocks) {
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
// ── Inject blocks into page HTML ──────────────────────────────────────────────
|
||||
// -- Inject blocks into page HTML ----------------------------------------------
|
||||
function pbInjectBlocks(rawHTML, blocksHTML) {
|
||||
// Try to inject into existing content area
|
||||
const parser = new DOMParser();
|
||||
@@ -2621,10 +2622,10 @@ function pbInjectBlocks(rawHTML, blocksHTML) {
|
||||
const s = new XMLSerializer();
|
||||
return '<!DOCTYPE html>' + s.serializeToString(doc.documentElement);
|
||||
}
|
||||
return rawHTML; // fallback — unchanged
|
||||
return rawHTML; // fallback -- unchanged
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────────────────────
|
||||
// -- Save ----------------------------------------------------------------------
|
||||
async function pbSave() {
|
||||
const btn = document.getElementById('pb-save-btn');
|
||||
const status = document.getElementById('pb-status');
|
||||
@@ -2659,7 +2660,7 @@ async function pbSave() {
|
||||
btn.innerHTML = '<span class="msym text-base">save</span> Save';
|
||||
}
|
||||
|
||||
// ── Wire into showPage ────────────────────────────────────────────────────────
|
||||
// -- Wire into showPage --------------------------------------------------------
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const origShowPage = window.showPage;
|
||||
if (origShowPage) {
|
||||
|
||||
Reference in New Issue
Block a user