|
26 | 26 | currentReport: null, |
27 | 27 | reportAnnotationRange: null, |
28 | 28 | highlightedReports: new Set(), |
| 29 | + reportContextMenu: null, |
29 | 30 | readingPollToken: 0, |
30 | 31 | wikiNodes: [], |
31 | 32 | wikiGraph: null, |
|
1823 | 1824 | if (route === "/api/reports/content") { |
1824 | 1825 | return { ok: true, report: demoReports.find((item) => item.report_id === url.searchParams.get("report_id")) || demoReports[0] }; |
1825 | 1826 | } |
| 1827 | + if (route === "/api/reports/delete") { |
| 1828 | + return { ok: true, deleted: true, report_id: body.report_id || "" }; |
| 1829 | + } |
1826 | 1830 | if (route === "/api/wiki/stats") { |
1827 | 1831 | return { ok: true, nodes: 112, edges: 248, citations: 436, wiki_dir: "data/wiki/user_demo" }; |
1828 | 1832 | } |
|
2718 | 2722 | renderReportList(state.reports, state.currentReport?.report_id || reportId); |
2719 | 2723 | } |
2720 | 2724 |
|
| 2725 | + function reportContextLabels(reportId) { |
| 2726 | + const isEn = responseLanguage() === "en"; |
| 2727 | + return { |
| 2728 | + highlight: state.highlightedReports.has(reportId) |
| 2729 | + ? (isEn ? "Remove highlight" : "取消高亮") |
| 2730 | + : (isEn ? "Highlight" : "高亮"), |
| 2731 | + delete: isEn ? "Delete report" : "删除报告" |
| 2732 | + }; |
| 2733 | + } |
| 2734 | + |
| 2735 | + function ensureReportContextMenu() { |
| 2736 | + if (state.reportContextMenu) return state.reportContextMenu; |
| 2737 | + const menu = document.createElement("div"); |
| 2738 | + menu.id = "reportContextMenu"; |
| 2739 | + menu.className = "report-context-menu hidden"; |
| 2740 | + menu.setAttribute("role", "menu"); |
| 2741 | + menu.innerHTML = ` |
| 2742 | + <button type="button" data-report-menu-action="highlight" role="menuitem"></button> |
| 2743 | + <button type="button" data-report-menu-action="delete" role="menuitem" class="danger"></button> |
| 2744 | + `; |
| 2745 | + menu.addEventListener("click", (event) => { |
| 2746 | + const button = event.target.closest("[data-report-menu-action]"); |
| 2747 | + if (!button) return; |
| 2748 | + const reportId = menu.dataset.reportId || ""; |
| 2749 | + const action = button.dataset.reportMenuAction; |
| 2750 | + hideReportContextMenu(); |
| 2751 | + if (action === "highlight") { |
| 2752 | + toggleReportCardHighlight(reportId); |
| 2753 | + } else if (action === "delete") { |
| 2754 | + runAction(() => deleteReportCard(reportId), "删除报告"); |
| 2755 | + } |
| 2756 | + }); |
| 2757 | + document.body.appendChild(menu); |
| 2758 | + state.reportContextMenu = menu; |
| 2759 | + return menu; |
| 2760 | + } |
| 2761 | + |
| 2762 | + function hideReportContextMenu() { |
| 2763 | + if (!state.reportContextMenu) return; |
| 2764 | + state.reportContextMenu.classList.add("hidden"); |
| 2765 | + state.reportContextMenu.dataset.reportId = ""; |
| 2766 | + } |
| 2767 | + |
| 2768 | + function showReportContextMenu(reportId, x, y) { |
| 2769 | + if (!reportId) return; |
| 2770 | + const menu = ensureReportContextMenu(); |
| 2771 | + const labels = reportContextLabels(reportId); |
| 2772 | + menu.dataset.reportId = reportId; |
| 2773 | + menu.querySelector('[data-report-menu-action="highlight"]').textContent = labels.highlight; |
| 2774 | + menu.querySelector('[data-report-menu-action="delete"]').textContent = labels.delete; |
| 2775 | + menu.classList.remove("hidden"); |
| 2776 | + const rect = menu.getBoundingClientRect(); |
| 2777 | + const left = Math.min(Math.max(8, x), window.innerWidth - rect.width - 8); |
| 2778 | + const top = Math.min(Math.max(8, y), window.innerHeight - rect.height - 8); |
| 2779 | + menu.style.left = `${left}px`; |
| 2780 | + menu.style.top = `${top}px`; |
| 2781 | + } |
| 2782 | + |
| 2783 | + async function deleteReportCard(reportId) { |
| 2784 | + if (!reportId) return; |
| 2785 | + const report = state.reports.find((item) => item.report_id === reportId) || state.currentReport || {}; |
| 2786 | + const title = report.title || ui().reports.defaultTitle; |
| 2787 | + const confirmText = responseLanguage() === "en" |
| 2788 | + ? `Delete this local reading report?\n\n${title}` |
| 2789 | + : `删除这个本地精读报告?\n\n${title}`; |
| 2790 | + if (!window.confirm(confirmText)) return; |
| 2791 | + await api("/api/reports/delete", { |
| 2792 | + method: "POST", |
| 2793 | + body: JSON.stringify({ report_id: reportId }) |
| 2794 | + }); |
| 2795 | + state.highlightedReports.delete(reportId); |
| 2796 | + saveReportCardHighlights(); |
| 2797 | + try { |
| 2798 | + window.localStorage.removeItem(reportAnnotationKey(reportId)); |
| 2799 | + } catch (error) { |
| 2800 | + console.warn("Unable to clear deleted report annotation", error); |
| 2801 | + } |
| 2802 | + if (state.currentReport?.report_id === reportId) clearReportViewer(); |
| 2803 | + showFeedbackToast("success", responseLanguage() === "en" ? "Report deleted" : "报告已删除", title); |
| 2804 | + await refreshReports({ keepSelection: false }); |
| 2805 | + } |
| 2806 | + |
2721 | 2807 | function loadReportAnnotation(reportId) { |
2722 | 2808 | if (!reportId) return ""; |
2723 | 2809 | try { |
|
2836 | 2922 | `).join(""); |
2837 | 2923 | } |
2838 | 2924 |
|
| 2925 | + function clearReportViewer() { |
| 2926 | + state.currentReport = null; |
| 2927 | + $("reportViewerTitle").textContent = ui().reports.defaultTitle; |
| 2928 | + $("reportViewerMeta").textContent = ""; |
| 2929 | + $("reportViewerBody").className = "markdown-body"; |
| 2930 | + $("reportViewerBody").innerHTML = ""; |
| 2931 | + $("clearReportAnnotationsBtn").disabled = true; |
| 2932 | + hideReportAnnotationToolbar(); |
| 2933 | + setReportLinks({}); |
| 2934 | + } |
| 2935 | + |
2839 | 2936 | async function refreshReports(options = {}) { |
2840 | 2937 | const scopedUser = $("reportsCurrentOnly").checked ? currentUser() : ""; |
2841 | 2938 | const query = $("reportQuery").value.trim(); |
|
2850 | 2947 | const activeId = options.activeReportId || (options.keepSelection && state.currentReport ? state.currentReport.report_id : state.reports[0]?.report_id || ""); |
2851 | 2948 | renderReportList(state.reports, activeId); |
2852 | 2949 | if (activeId) await loadReportContent(activeId, true); |
| 2950 | + else clearReportViewer(); |
2853 | 2951 | } |
2854 | 2952 |
|
2855 | 2953 | async function pollReadingTask(taskId, activeReportId = "") { |
|
5407 | 5505 | runAction(() => refreshReports({ keepSelection: false }), "筛选报告"); |
5408 | 5506 | }); |
5409 | 5507 | $("reportsList").addEventListener("click", (event) => { |
| 5508 | + hideReportContextMenu(); |
5410 | 5509 | const row = event.target.closest("[data-report-id]"); |
5411 | 5510 | if (row) runAction(() => loadReportContent(row.dataset.reportId), "打开报告"); |
5412 | 5511 | }); |
5413 | 5512 | $("reportsList").addEventListener("contextmenu", (event) => { |
5414 | 5513 | const row = event.target.closest("[data-report-id]"); |
5415 | 5514 | if (!row) return; |
5416 | 5515 | event.preventDefault(); |
5417 | | - toggleReportCardHighlight(row.dataset.reportId); |
| 5516 | + const reportId = row.dataset.reportId; |
| 5517 | + runAction(async () => { |
| 5518 | + await loadReportContent(reportId, true); |
| 5519 | + showReportContextMenu(reportId, event.clientX, event.clientY); |
| 5520 | + }, "打开报告菜单"); |
| 5521 | + }); |
| 5522 | + document.addEventListener("click", (event) => { |
| 5523 | + if (!event.target.closest("#reportContextMenu")) hideReportContextMenu(); |
| 5524 | + }); |
| 5525 | + document.addEventListener("keydown", (event) => { |
| 5526 | + if (event.key === "Escape") hideReportContextMenu(); |
5418 | 5527 | }); |
| 5528 | + window.addEventListener("scroll", hideReportContextMenu, true); |
5419 | 5529 | ["openReportAbsBtn", "openReportPdfBtn", "openReportDocBtn"].forEach((id) => { |
5420 | 5530 | $(id).addEventListener("click", () => { |
5421 | 5531 | const href = $(id).dataset.href; |
|
0 commit comments