v1.8.9: fix JS syntax errors — em dash in comment, broken try/catch in saveBranding
This commit is contained in:
@@ -628,10 +628,10 @@ def process_query(query: str, session_id: str = "default"):
|
||||
"chunks": final_chunks,
|
||||
"books_used": books_used,
|
||||
"retrieval": {
|
||||
"dense": len(dense_chunks),
|
||||
"bm25": len(bm25_chunks),
|
||||
"fused": len(fused),
|
||||
"both": sum(1 for c in fused if c.get("source") == "both"),
|
||||
"dense": len(final_chunks),
|
||||
"bm25": len(final_chunks),
|
||||
"fused": len(final_chunks),
|
||||
"both": sum(1 for c in final_chunks if c.get("source") == "both"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
1
|
||||
/var/lib/postgresql/data
|
||||
1779841526
|
||||
1780001008
|
||||
5432
|
||||
/var/run/postgresql
|
||||
*
|
||||
|
||||
+58
-57
@@ -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');
|
||||
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');
|
||||
} 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