(function () { // Theme toggle initialization const themeToggle = document.getElementById("theme-toggle"); if (themeToggle) { const savedTheme = localStorage.getItem("theme") || "light"; if (savedTheme === "dark") { document.body.classList.add("dark-theme"); themeToggle.querySelector(".theme-icon").textContent = "☀️"; themeToggle.querySelector(".theme-text").textContent = "المظهر المضيء"; } themeToggle.addEventListener("click", () => { document.body.classList.toggle("dark-theme"); const isDark = document.body.classList.contains("dark-theme"); localStorage.setItem("theme", isDark ? "dark" : "light"); themeToggle.querySelector(".theme-icon").textContent = isDark ? "\u2600" : "\u263D"; themeToggle.querySelector(".theme-text").textContent = isDark ? "المظهر المضيء" : "المظهر الداكن"; }); } const form = document.getElementById("search-form"); const submitBtn = document.getElementById("submit-btn"); const btnText = document.getElementById("btn-text"); const btnSpinner = document.getElementById("btn-spinner"); const progressSection = document.getElementById("progress-section"); const searchPhase = document.getElementById("search-phase"); const writePhase = document.getElementById("write-phase"); const writeBtn = document.getElementById("write-btn"); const writeBtnText = document.getElementById("write-btn-text"); const writeSpinner = document.getElementById("write-spinner"); const writeInfo = document.getElementById("write-info"); const writeProgressSection = document.getElementById("write-progress-section"); const progressFill = document.getElementById("progress-fill"); const writeFill = document.getElementById("write-fill"); const progressPercent = document.getElementById("progress-percent"); const writePercent = document.getElementById("write-percent"); const statusText = document.getElementById("status-text"); const writeStatusText = document.getElementById("write-status-text"); const logContent = document.getElementById("log-content"); const sectionCards = document.getElementById("section-cards"); const resultsSection = document.getElementById("results-section"); const resultTitle = document.getElementById("result-title"); const summary = document.getElementById("summary"); const sectionDetail = document.getElementById("section-detail"); const filePaths = document.getElementById("file-paths"); const reviewPhase = document.getElementById("review-phase"); const reviewBtn = document.getElementById("review-btn"); const reviewBtnText = document.getElementById("review-btn-text"); const reviewSpinner = document.getElementById("review-spinner"); const reviewProgressSection = document.getElementById("review-progress-section"); const reviewFill = document.getElementById("review-fill"); const reviewPercent = document.getElementById("review-percent"); const reviewStatusText = document.getElementById("review-status-text"); const studyPreview = document.getElementById("study-preview"); const studyContent = document.getElementById("study-content"); const previewActions = document.getElementById("preview-actions"); const rawPathRow = document.getElementById("raw-path-row"); const rawPathEl = document.getElementById("raw-path"); const studyPathRow = document.getElementById("study-path-row"); const studyPathEl = document.getElementById("study-path"); const errorSection = document.getElementById("error-section"); const errorText = document.getElementById("error-text"); const restoreNotice = document.getElementById("restore-notice"); const downloadSuperPromptBtn = document.getElementById("download-super-prompt-btn"); const copySuperPromptBtn = document.getElementById("copy-super-prompt-btn"); const copyAbsPathBtn = document.getElementById("copy-abs-path-btn"); if (copyAbsPathBtn) { copyAbsPathBtn.addEventListener("click", function () { var pathVal = document.getElementById("folder-abs-path")?.textContent || ""; if (pathVal) { navigator.clipboard.writeText(pathVal).then(function () { var origText = copyAbsBtn.textContent; copyAbsPathBtn.textContent = "✅ تم النسخ!"; setTimeout(function () { copyAbsPathBtn.textContent = origText; }, 2000); }); } }); } const importText = document.getElementById("import-text"); const importBtn = document.getElementById("import-btn"); const importBtnText = document.getElementById("import-btn-text"); const importSpinner = document.getElementById("import-spinner"); const manualWriteActions = document.getElementById("manual-write-actions"); const newSessionBtn = document.getElementById("new-session-btn"); const cancelSearchBtn = document.getElementById("cancel-search-btn"); const auditSection = document.getElementById("audit-section"); const auditBadgeContainer = document.getElementById("audit-badge-container"); const auditStatsGrid = document.getElementById("audit-stats-grid"); const auditIssuesList = document.getElementById("audit-issues-list"); const pastJobsList = document.getElementById("past-jobs-list"); let currentJobId = null; let currentStudyFullText = null; let currentStudyRenderedLength = 0; var LOAD_MORE_CHUNK = 4000; // C6: API key override from UI let apiKeyOverride = ""; const apiKeyInput = document.getElementById("api-key-input"); if (apiKeyInput) { apiKeyInput.addEventListener("change", function() { apiKeyOverride = apiKeyInput.value.trim(); }); } const STORAGE_KEY = "alfaz_job_id"; const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ""; async function loadPastJobs() { if (!pastJobsList) return; // Show skeleton while loading pastJobsList.innerHTML = '
' + '' + ''; try { var resp = await fetch("/flask/jobs"); var data = await resp.json(); if (!data || data.length === 0) { pastJobsList.innerHTML = 'لا توجد جلسات نشطة حالياً.
'; return; } var html = ""; data.forEach(function (job) { var jobClass = "job-item" + (job.done ? " completed" : ""); var dateStr = ""; if (job.created_at) { var date = new Date(job.created_at * 1000); dateStr = date.toLocaleTimeString("ar-EG", { hour: '2-digit', minute: '2-digit' }) + " " + date.toLocaleDateString("ar-EG", { month: 'short', day: 'numeric' }); } var statusLabel = job.status || (job.done ? "مكتمل" : "جاري العمل..."); html += 'لا توجد دراسات مكتملة بعد.
'; return; } var html = ""; data.forEach(function (s) { var statusClass = s.status === "مكتمل" ? "status-done" : s.status === "يحتاج مراجعة" ? "status-warn" : "status-pending"; var dlBtns = ""; if (s.study_dl) { dlBtns += 'MD'; } if (s.docx_dl) { dlBtns += 'Word'; } var meta = s.word_count ? s.word_count + " كلمة" : ""; if (s.total_results) meta += (meta ? " · " : "") + s.total_results + " نتيجة"; var clickable = ' style="cursor:pointer"'; // Store the study path for fetching full text on click (not the truncated text) var dataAttrs = ' data-term="' + esc(s.term) + '" data-cat="' + esc(s.category_key) + '"' + ' data-job-id="' + esc(s.job_id || "") + '"' + ' data-study-path="' + esc(s.study_path || "") + '"' + ' data-study-dl="' + esc(s.study_dl || "") + '"' + ' data-docx-dl="' + esc(s.docx_dl || "") + '"' + ' data-status="' + esc(s.status || "") + '"'; html += 'فشل تحميل الدراسات.
'; console.error("Failed to load completed studies", e); } } async function deleteJob(jobId) { if (!confirm("هل أنت متأكد من حذف هذه الجلسة؟")) return; try { var resp = await fetch("/flask/delete/" + jobId, { method: "DELETE", headers: { "X-CSRFToken": csrfToken } }); var data = await resp.json(); if (data.error) { showError(data.error); return; } if (currentJobId === jobId) { currentJobId = null; clearJobId(); progressSection.classList.add("hidden"); searchPhase.classList.add("hidden"); writePhase.classList.add("hidden"); reviewPhase.classList.add("hidden"); resultsSection.classList.add("hidden"); studyPreview.classList.add("hidden"); filePaths.classList.add("hidden"); errorSection.classList.add("hidden"); submitBtn.disabled = false; btnText.classList.remove("hidden"); btnSpinner.classList.add("hidden"); } loadPastJobs(); } catch (e) { showError("فشل حذف الجلسة: " + e.message); } } function loadJobDetails(jid) { disconnectSSE(); fetch("/flask/status/" + jid) .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(function (data) { if (data.error) { showError(data.error); return; } currentJobId = jid; saveJobId(jid); // Reset and hide all views before restoring progressSection.classList.add("hidden"); searchPhase.classList.add("hidden"); writePhase.classList.add("hidden"); reviewPhase.classList.add("hidden"); resultsSection.classList.add("hidden"); studyPreview.classList.add("hidden"); filePaths.classList.add("hidden"); restoreNotice.classList.add("hidden"); errorSection.classList.add("hidden"); restoreUI(data); }) .catch(function (e) { showError("هذه الجلسة لم تعد متاحة. ابدأ بحثاً جديداً."); clearJobId(); currentJobId = null; loadPastJobs(); }); } function saveJobId(id) { try { localStorage.setItem(STORAGE_KEY, id); } catch (e) {} } function clearJobId() { try { localStorage.removeItem(STORAGE_KEY); } catch (e) {} } function loadSavedJob() { var saved = null; try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {} if (!saved) return; fetch("/flask/status/" + saved) .then(function (r) { return r.json(); }) .then(function (data) { if (data.error) { clearJobId(); return; } currentJobId = saved; restoreUI(data); }) .catch(function () { clearJobId(); }); } function restoreUI(data) { progressSection.classList.remove("hidden"); progressSection.classList.add("fade-in"); restoreNotice.classList.remove("hidden"); if (data.search_done) { onSearchComplete(data, true); if (data.write_done && !data.write_error) { onWriteComplete(data, true); if (data.review_in_progress) { reviewPhase.classList.remove("hidden"); reviewPhase.classList.add("fade-in"); reviewProgressSection.classList.remove("hidden"); reviewBtn.disabled = true; reviewStatusText.textContent = data.status || "جاري المراجعة..."; reviewFill.style.width = (data.review_progress || 0) + "%"; reviewPercent.textContent = (data.review_progress || 0) + "%"; connectSSE(currentJobId); } else if (data.review_done) { onReviewComplete(data); } } else if (data.write_error) { writePhase.classList.remove("hidden"); writePhase.classList.add("fade-in"); if (manualWriteActions) { manualWriteActions.classList.add("hidden"); } writeProgressSection.classList.add("hidden"); writeBtn.disabled = false; writeBtnText.textContent = "✍️ اكتب الدراسة (إعادة محاولة)"; writeSpinner.classList.add("hidden"); showError("حدث خطأ سابق أثناء الكتابة: " + data.write_error); } else if (data.write_progress > 0) { writePhase.classList.remove("hidden"); writePhase.classList.add("fade-in"); if (manualWriteActions) { manualWriteActions.classList.add("hidden"); } writeProgressSection.classList.remove("hidden"); writeBtn.disabled = true; writeStatusText.textContent = data.status || "جاري الكتابة..."; writeFill.style.width = (data.write_progress || 0) + "%"; writePercent.textContent = (data.write_progress || 0) + "%"; connectSSE(currentJobId); } else { // Search is done, ready for writing study writePhase.classList.remove("hidden"); writePhase.classList.add("fade-in"); writeBtn.disabled = false; writeBtnText.textContent = "✍️ اكتب الدراسة"; writeSpinner.classList.add("hidden"); writeProgressSection.classList.add("hidden"); } } else if (data.search_progress > 0) { searchPhase.classList.remove("hidden"); searchPhase.classList.add("fade-in"); updateSearchUI(data); connectSSE(currentJobId); } } document.querySelectorAll(".section-card").forEach(function (card) { card.addEventListener("click", function () { document.querySelectorAll(".section-card").forEach(function (c) { c.classList.remove("active"); }); card.classList.add("active"); }); }); form.addEventListener("submit", async function (e) { e.preventDefault(); var term = document.getElementById("term").value.trim(); if (!term) return; submitBtn.disabled = true; btnText.classList.add("hidden"); btnSpinner.classList.remove("hidden"); var category = document.getElementById("category").value; var depth = document.getElementById("depth").value; var section = document.querySelector('input[name="section"]:checked') ? document.querySelector('input[name="section"]:checked').value : "all"; var sources = ["shamela"]; progressSection.classList.remove("hidden"); progressSection.classList.add("fade-in"); searchPhase.classList.remove("hidden"); searchPhase.classList.add("fade-in"); writePhase.classList.add("hidden"); resultsSection.classList.add("hidden"); errorSection.classList.add("hidden"); studyPreview.classList.add("hidden"); filePaths.classList.add("hidden"); restoreNotice.classList.add("hidden"); try { var resp = await fetch("/flask/start", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": csrfToken }, body: JSON.stringify({ term: term, category: category, mode: depth, section: section, sources: sources, api_key: apiKeyOverride || undefined, }), }); var data = await resp.json(); if (data.error) { showError(data.error); submitBtn.disabled = false; btnText.classList.remove("hidden"); btnSpinner.classList.add("hidden"); return; } currentJobId = data.job_id; saveJobId(data.job_id); loadPastJobs(); connectSSE(data.job_id); if (cancelSearchBtn) { cancelSearchBtn.classList.remove("hidden"); } } catch (err) { showError("فشل الاتصال بالخادم: " + err.message); submitBtn.disabled = false; btnText.classList.remove("hidden"); btnSpinner.classList.add("hidden"); } }); writeBtn.addEventListener("click", async function () { if (!currentJobId) return; writeBtn.disabled = true; writeBtnText.textContent = "جاري الكتابة..."; writeSpinner.classList.remove("hidden"); writeProgressSection.classList.remove("hidden"); writeFill.style.width = "0%"; writePercent.textContent = "0%"; writeStatusText.textContent = "جاري بدء الكتابة..."; if (manualWriteActions) { manualWriteActions.classList.add("hidden"); } try { var body = {}; if (apiKeyOverride) body.api_key = apiKeyOverride; var resp = await fetch("/flask/write/" + currentJobId, { method: "POST", headers: { "X-CSRFToken": csrfToken, "Content-Type": "application/json" }, body: JSON.stringify(body), }); var data = await resp.json(); if (data.error) { showError(data.error); writeBtn.disabled = false; writeBtnText.textContent = "✍️ اكتب الدراسة"; writeSpinner.classList.add("hidden"); writeProgressSection.classList.add("hidden"); if (manualWriteActions) { manualWriteActions.classList.remove("hidden"); } return; } loadPastJobs(); connectSSE(currentJobId); } catch (err) { showError("فشل بدء الكتابة: " + err.message); writeBtn.disabled = false; writeBtnText.textContent = "✍️ اكتب الدراسة"; writeSpinner.classList.add("hidden"); writeProgressSection.classList.add("hidden"); if (manualWriteActions) { manualWriteActions.classList.remove("hidden"); } } }); if (importBtn) { importBtn.addEventListener("click", async function () { if (!currentJobId) return; var text = importText.value.trim(); if (!text || text.length < 100) { alert("الرجاء إلصاق نص الدراسة كاملاً أولاً (100 حرف على الأقل)."); return; } importBtn.disabled = true; importSpinner.classList.remove("hidden"); importBtnText.textContent = "جاري استيراد ومعالجة الدراسة..."; try { var resp = await fetch("/flask/import/" + currentJobId, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": csrfToken }, body: JSON.stringify({ text: text }) }); var res = await resp.json(); if (res.error) { alert("خطأ: " + res.error); } else { importText.value = ""; loadJobDetails(currentJobId); loadPastJobs(); } } catch (err) { alert("فشل الاتصال بالخادم: " + err.message); } finally { importBtn.disabled = false; importSpinner.classList.add("hidden"); importBtnText.textContent = "حفظ واستيراد الدراسة اليدوية"; } }); } if (newSessionBtn) { newSessionBtn.addEventListener("click", function () { clearJobId(); currentJobId = null; // Reset form document.getElementById("term").value = ""; // Reset UI views progressSection.classList.add("hidden"); searchPhase.classList.add("hidden"); writePhase.classList.add("hidden"); reviewPhase.classList.add("hidden"); resultsSection.classList.add("hidden"); studyPreview.classList.add("hidden"); filePaths.classList.add("hidden"); restoreNotice.classList.add("hidden"); errorSection.classList.add("hidden"); // Reset progress bars and status progressFill.style.width = "0%"; progressPercent.textContent = "0%"; statusText.textContent = "جاري التجهيز..."; logContent.innerHTML = ""; sectionCards.innerHTML = ""; writeFill.style.width = "0%"; writePercent.textContent = "0%"; writeStatusText.textContent = "جاري الكتابة..."; // Enable search submit submitBtn.disabled = false; btnText.classList.remove("hidden"); btnSpinner.classList.add("hidden"); loadPastJobs(); }); } if (cancelSearchBtn) { cancelSearchBtn.addEventListener("click", async function () { if (!currentJobId) return; if (!confirm("هل تريد إلغاء البحث؟ يمكنك تعديل المعاملات وإعادة التشغيل.")) return; cancelSearchBtn.disabled = true; cancelSearchBtn.textContent = "جاري الإلغاء..."; try { await fetch("/flask/cancel/" + currentJobId, { method: "POST", headers: { "X-CSRFToken": csrfToken }, }); } catch (e) { // Ignore network errors — job may already be cleaned up } // Reset UI disconnectSSE(); currentJobId = null; clearJobId(); progressSection.classList.add("hidden"); searchPhase.classList.add("hidden"); writePhase.classList.add("hidden"); reviewPhase.classList.add("hidden"); resultsSection.classList.add("hidden"); studyPreview.classList.add("hidden"); filePaths.classList.add("hidden"); errorSection.classList.add("hidden"); restoreNotice.classList.add("hidden"); progressFill.style.width = "0%"; progressPercent.textContent = "0%"; statusText.textContent = "جاري التجهيز..."; logContent.innerHTML = ""; sectionCards.innerHTML = ""; submitBtn.disabled = false; btnText.classList.remove("hidden"); btnSpinner.classList.add("hidden"); cancelSearchBtn.classList.add("hidden"); cancelSearchBtn.disabled = false; cancelSearchBtn.textContent = "إلغاء"; loadPastJobs(); }); } // ── SSE (Server-Sent Events) — replaces polling ──────────────── var currentEventSource = null; function connectSSE(jobId) { if (currentEventSource) { currentEventSource.close(); } var es = new EventSource("/flask/stream/" + jobId); currentEventSource = es; es.addEventListener("init", function (e) { try { var data = JSON.parse(e.data); updateSearchUI(data); if (data.search_done) { onSearchComplete(data, true); } if (data.write_done) { onWriteComplete(data, true); } } catch (err) {} }); es.addEventListener("search_progress", function (e) { try { var data = JSON.parse(e.data); statusText.textContent = data.status; progressFill.style.width = data.progress + "%"; progressPercent.textContent = data.progress + "%"; if (data.log) { appendLog(data.log); } } catch (err) {} }); es.addEventListener("search_done", function (e) { try { var data = JSON.parse(e.data); onSearchComplete(data); loadPastJobs(); } catch (err) {} }); es.addEventListener("write_progress", function (e) { try { var data = JSON.parse(e.data); writeStatusText.textContent = data.status; writeFill.style.width = data.progress + "%"; writePercent.textContent = data.progress + "%"; if (data.log) { appendLog(data.log); } } catch (err) {} }); es.addEventListener("write_done", function (e) { try { var data = JSON.parse(e.data); if (data.write_error) { showError(data.write_error); writeBtn.disabled = false; writeBtnText.textContent = "إعادة المحاولة"; writeSpinner.classList.add("hidden"); } else { onWriteComplete(data); } loadPastJobs(); } catch (err) {} }); es.addEventListener("review_progress", function (e) { try { var data = JSON.parse(e.data); reviewStatusText.textContent = data.status; reviewFill.style.width = data.progress + "%"; reviewPercent.textContent = data.progress + "%"; } catch (err) {} }); es.addEventListener("review_done", function (e) { try { var data = JSON.parse(e.data); if (data.review_error) { showError("المراجعة: " + data.review_error); reviewBtn.disabled = false; reviewBtnText.textContent = "إعادة المراجعة"; reviewSpinner.classList.add("hidden"); } else { onReviewComplete(data); } loadPastJobs(); } catch (err) {} }); es.addEventListener("done", function (e) { es.close(); currentEventSource = null; }); es.addEventListener("keepalive", function () {}); es.onerror = function () { // Auto-reconnect is built into EventSource, but close after 60s of errors setTimeout(function () { if (es.readyState === EventSource.CLOSED) return; // If still failing, fall back to polling es.close(); currentEventSource = null; pollSearchFallback(jobId); }, 60000); }; return es; } function disconnectSSE() { if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } } // ── Fallback polling (only if SSE fails) ───────────────────── function pollSearchFallback(jobId) { var interval = setInterval(async function () { try { var resp = await fetch("/flask/status/" + jobId); var data = await resp.json(); if (data.error) { clearInterval(interval); showError(data.error); return; } updateSearchUI(data); if (data.search_done) { clearInterval(interval); loadPastJobs(); if (data.write_done) { onWriteComplete(data); if (data.review_done) { onReviewComplete(data); } else if (data.review_in_progress) { pollReviewFallback(jobId); } } else if (data.write_progress > 0) { onSearchComplete(data, true); pollWriteFallback(jobId); } else { onSearchComplete(data); } } } catch (err) { clearInterval(interval); showError("خطأ في الاتصال: " + err.message); } }, 5000); } function pollWriteFallback(jobId) { var interval = setInterval(async function () { try { var resp = await fetch("/flask/status/" + jobId); var data = await resp.json(); if (data.error) { clearInterval(interval); showError(data.error); return; } writeStatusText.textContent = data.status; writeFill.style.width = data.write_progress + "%"; writePercent.textContent = data.write_progress + "%"; if (data.write_done) { clearInterval(interval); loadPastJobs(); if (data.write_error) { showError(data.write_error); writeBtn.disabled = false; writeBtnText.textContent = "إعادة المحاولة"; writeSpinner.classList.add("hidden"); } else { onWriteComplete(data); if (data.review_in_progress) { pollReviewFallback(jobId); } } } } catch (err) { clearInterval(interval); showError("خطأ في الاتصال: " + err.message); } }, 5000); } function pollReviewFallback(jobId) { var interval = setInterval(async function () { try { var resp = await fetch("/flask/status/" + jobId); var data = await resp.json(); if (data.error) { clearInterval(interval); showError(data.error); return; } reviewStatusText.textContent = data.status; reviewFill.style.width = data.review_progress + "%"; reviewPercent.textContent = data.review_progress + "%"; if (data.review_done) { clearInterval(interval); loadPastJobs(); if (data.review_error) { showError("المراجعة: " + data.review_error); reviewBtn.disabled = false; reviewBtnText.textContent = "إعادة المراجعة"; reviewSpinner.classList.add("hidden"); } else { onReviewComplete(data); } } } catch (err) { clearInterval(interval); showError("خطأ في الاتصال: " + err.message); } }, 5000); } function updateSearchUI(data) { statusText.textContent = data.status; progressFill.style.width = data.search_progress + "%"; progressPercent.textContent = data.search_progress + "%"; if (data.log && data.log.length > 0) { var logHtml = ""; data.log.forEach(function (entry) { logHtml += 'اللفظ: ' + esc(data.term) + "
"; html += 'عدد النتائج: ' + (data.total_results || "—") + "
"; html += 'نتائج عليها دليل نسبة للقطان: ' + (data.qattan_evidence_count || 0) + "
"; html += 'نتائج تحتاج مراجعة: ' + (data.needs_review_count || 0) + "
"; summary.innerHTML = html; if (data.section_details && Object.keys(data.section_details).length > 0) { renderBadges(data.section_details, sectionDetail); } if (data.study_text) { studyPreview.classList.remove("hidden"); studyPreview.classList.add("fade-in"); reviewPhase.classList.remove("hidden"); reviewPhase.classList.add("fade-in"); var actionsHtml = ""; if (data.study_file) { actionsHtml += 'تحميل MD'; } if (data.study_file) { var dlPath = data.study_file.replace(/^\//, ''); actionsHtml += 'تحميل Word'; } if (data.prompt_path) { actionsHtml += 'التعليمات'; } if (data.super_prompt_path) { actionsHtml += 'البرومبت الخارق'; } if (data.study_reviewed) { actionsHtml += 'تمت المراجعة'; } previewActions.innerHTML = actionsHtml; // Update full preview button with term name var viewFullBtn = previewActions.querySelector(".view-full-btn"); if (viewFullBtn && data.term) { viewFullBtn.setAttribute("data-term", data.term); } setStudyContent(data.study_text); } if (data.audit) { renderAudit(data.audit); } else { auditSection.classList.add("hidden"); } loadCompletedStudies(); } function renderBadges(sections, container) { var labels = { 1: "لغوية", 2: "اصطلاحية", 3: "القطان", 4: "مقارنة", 5: "خلاصة", }; var html = ""; Object.keys(sections).forEach(function (key) { var s = sections[key]; html += '" + esc(codeLines.join("\n")) + "\n";
}
inCodeBlock = false;
codeFence = null;
codeLines = [];
}
function inlineFormat(text) {
// Bold **text**
text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
// Italic *text*
text = text.replace(/\*([^*]+)\*/g, "$1");
// Links [text](url) — sanitize URL to prevent javascript: attacks
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function (m, label, url) {
var safeUrl = url.replace(/"/flask/g, '"').replace(/javascript:/gi, '');
return '' + label + '';
});
// Plain https:// links → clickable
text = text.replace(/(https:\/\/shamela\.ws\/\S+)/g,
'🔗 رابط الشاملة');
// Citation-style: (يحتاج توثيقاً)
text = text.replace(/\(يحتاج توثيقاً\)/g,
'(يحتاج توثيقاً)');
return text;
}
lines.forEach(function(line) {
var trimmed = line.trim();
// Fenced code block handling
var fenceMatch = trimmed.match(/^(```|~~~)(.*)/);
if (fenceMatch) {
if (inCodeBlock) {
// Closing fence — flush the code block
if (codeFence === fenceMatch[1]) {
flushCodeBlock();
return;
} else {
// Mismatched fence — treat as code line
codeLines.push(line);
return;
}
} else {
// Opening fence — flush any pending paragraph, start code block
closePara();
inCodeBlock = true;
codeFence = fenceMatch[1];
// Skip the info string (e.g. ```python) on the opening line
return;
}
}
if (inCodeBlock) {
codeLines.push(line);
return;
}
if (!trimmed) {
closePara();
return;
}
// Headings
var hm = trimmed.match(/^(#{1,5})\s+(.+)/);
if (hm) {
closePara();
var lvl = Math.min(hm[1].length, 6);
html += ""; inPara = true; } else { html += " "; } html += inlineFormat(esc(trimmed)); }); flushCodeBlock(); closePara(); return html; } function setStudyContent(text) { if (!text) return; currentStudyFullText = text.trim(); currentStudyRenderedLength = LOAD_MORE_CHUNK; var truncated = currentStudyFullText.substring(0, LOAD_MORE_CHUNK); var html = renderMarkdown(truncated); if (currentStudyFullText.length > LOAD_MORE_CHUNK) { var remaining = currentStudyFullText.length - LOAD_MORE_CHUNK; html += '