diff --git a/rust/crates/mnote-web/browser/document-resource-tab-runtime.js b/rust/crates/mnote-web/browser/document-resource-tab-runtime.js index b5cd9176..e63aec50 100644 --- a/rust/crates/mnote-web/browser/document-resource-tab-runtime.js +++ b/rust/crates/mnote-web/browser/document-resource-tab-runtime.js @@ -690,11 +690,18 @@ export const createResourceTabRuntime = (dependencies = {}) => { const entry = resourceTabRegistry.get(activeResource); const documentId = String(entry?.ownerDocumentId || entry?.documentId || '').trim(); if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`; + const sourceKind = String(entry?.sourceKind || currentWebShellSourceKind() || '').trim(); + const rootUri = String(entry?.rootUri || currentWebShellRootUri() || '').trim(); + const resourcePath = String(entry?.path || entry?.session?.resourcePath || '').trim(); + if (sourceKind) url.searchParams.set('sourceKind', sourceKind); + if (rootUri) url.searchParams.set('rootUri', rootUri); + if (resourcePath) url.searchParams.set('resourcePath', resourcePath); url.searchParams.set('resourceTab', activeResource); } else { const documentId = currentWebShellDocumentId(); if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`; url.searchParams.delete('resourceTab'); + url.searchParams.delete('resourcePath'); } replaceUrlState(url); }; @@ -1085,6 +1092,59 @@ export const createResourceTabRuntime = (dependencies = {}) => { overlay.style.height = `${Math.max(1, bbox.y1 - bbox.y0)}px`; }; + const applyPdfEvidenceOverlayToCanvas = (canvas, locator) => { + if (!(canvas instanceof HTMLCanvasElement)) return null; + const frame = canvas.parentElement instanceof HTMLElement && canvas.parentElement.hasAttribute('data-mnote-pdf-page-frame') + ? canvas.parentElement + : null; + if (!(frame instanceof HTMLElement)) return null; + const bbox = normalizeEvidenceBBox(locator?.bbox); + let overlay = frame.querySelector('[data-mnote-evidence-bbox-highlight]'); + if (!bbox) { + if (overlay instanceof HTMLElement) overlay.hidden = true; + return null; + } + const viewport = canvas.__mnotePdfViewport; + if (!viewport || typeof viewport.convertToViewportRectangle !== 'function') return null; + if (!(overlay instanceof HTMLElement)) { + overlay = document.createElement('div'); + overlay.className = 'mnote-resource-tab-bbox-highlight'; + overlay.setAttribute('data-mnote-evidence-bbox-highlight', 'true'); + frame.append(overlay); + } + const normalizedMineruBox = bbox.x0 >= 0 && bbox.y0 >= 0 && bbox.x1 <= 1000 && bbox.y1 <= 1000; + let x; + let y; + let width; + let height; + if (normalizedMineruBox) { + x = Math.min(bbox.x0, bbox.x1) / 1000 * viewport.width; + y = Math.min(bbox.y0, bbox.y1) / 1000 * viewport.height; + width = Math.max(1, Math.abs(bbox.x1 - bbox.x0) / 1000 * viewport.width); + height = Math.max(1, Math.abs(bbox.y1 - bbox.y0) / 1000 * viewport.height); + } else { + const rect = viewport.convertToViewportRectangle([bbox.x0, bbox.y0, bbox.x1, bbox.y1]); + x = Math.min(rect[0], rect[2]); + y = Math.min(rect[1], rect[3]); + width = Math.max(1, Math.abs(rect[2] - rect[0])); + height = Math.max(1, Math.abs(rect[3] - rect[1])); + } + overlay.hidden = false; + overlay.style.left = `${Math.max(0, x)}px`; + overlay.style.top = `${Math.max(0, y)}px`; + overlay.style.width = `${width}px`; + overlay.style.height = `${height}px`; + return overlay; + }; + + const scrollPdfEvidenceTargetIntoView = (canvas, overlay) => { + const target = overlay instanceof HTMLElement && !overlay.hidden + ? overlay + : (canvas?.parentElement instanceof HTMLElement ? canvas.parentElement : canvas); + if (!(target instanceof HTMLElement)) return; + window.setTimeout(() => target.scrollIntoView({ block: 'center', inline: 'nearest' }), 0); + }; + const applyEvidenceLocatorToTextPanel = (entry, locator) => { if (!(entry?.panel instanceof HTMLElement) || !locator) return; const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]'); @@ -1119,8 +1179,12 @@ export const createResourceTabRuntime = (dependencies = {}) => { entry.panel.querySelectorAll('canvas.mnote-pdf-page[data-mnote-evidence-page="true"]').forEach((node) => { if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-page'); }); + entry.panel.querySelectorAll('[data-mnote-evidence-bbox-highlight]').forEach((node) => { + if (node instanceof HTMLElement) node.hidden = true; + }); canvas.setAttribute('data-mnote-evidence-page', 'true'); - canvas.scrollIntoView({ block: 'center', inline: 'nearest' }); + const overlay = applyPdfEvidenceOverlayToCanvas(canvas, locator); + scrollPdfEvidenceTargetIntoView(canvas, overlay); }; const applyEvidenceLocatorToEntry = (entry, input = {}) => { @@ -1328,16 +1392,23 @@ export const createResourceTabRuntime = (dependencies = {}) => { const scale = Math.max(0.6, Math.min(2.4, availableWidth / baseViewport.width)); const viewport = page.getViewport({ scale }); const outputScale = Math.min(2.5, Math.max(2, window.devicePixelRatio || 1)); + const frame = document.createElement('div'); + frame.setAttribute('data-mnote-pdf-page-frame', 'true'); + frame.style.position = 'relative'; + frame.style.width = `${Math.floor(viewport.width)}px`; + frame.style.maxWidth = '100%'; + frame.style.margin = '0 auto 14px'; const canvas = document.createElement('canvas'); canvas.className = 'mnote-pdf-page'; canvas.setAttribute('data-page-number', String(pageNumber)); + canvas.__mnotePdfViewport = viewport; canvas.width = Math.floor(viewport.width * outputScale); canvas.height = Math.floor(viewport.height * outputScale); canvas.style.display = 'block'; canvas.style.maxWidth = '100%'; canvas.style.width = `${Math.floor(viewport.width)}px`; canvas.style.height = `${Math.floor(viewport.height)}px`; - canvas.style.margin = '0 auto 14px'; + canvas.style.margin = '0'; canvas.style.background = '#fff'; canvas.style.border = '1px solid #d8d8d2'; canvas.style.boxShadow = '0 2px 10px rgba(25, 25, 22, .08)'; @@ -1351,25 +1422,12 @@ export const createResourceTabRuntime = (dependencies = {}) => { const evidencePage = Number(evidenceLocator?.page || 0); if (evidencePage === pageNumber) { canvas.setAttribute('data-mnote-evidence-page', 'true'); - const bbox = normalizeEvidenceBBox(evidenceLocator?.bbox); - if (bbox) { - const rect = viewport.convertToViewportRectangle([bbox.x0, bbox.y0, bbox.x1, bbox.y1]); - const x = Math.min(rect[0], rect[2]); - const y = Math.min(rect[1], rect[3]); - const width = Math.max(1, Math.abs(rect[2] - rect[0])); - const height = Math.max(1, Math.abs(rect[3] - rect[1])); - context.save(); - context.scale(outputScale, outputScale); - context.fillStyle = 'rgba(37, 99, 235, 0.18)'; - context.strokeStyle = 'rgba(37, 99, 235, 0.9)'; - context.lineWidth = 2; - context.fillRect(x, y, width, height); - context.strokeRect(x, y, width, height); - context.restore(); - } } - if (entry.inlinePdfDocument === pdf) viewer.append(canvas); - if (evidencePage === pageNumber) window.setTimeout(() => canvas.scrollIntoView({ block: 'center', inline: 'nearest' }), 0); + frame.append(canvas); + let evidenceOverlay = null; + if (evidencePage === pageNumber) evidenceOverlay = applyPdfEvidenceOverlayToCanvas(canvas, evidenceLocator); + if (entry.inlinePdfDocument === pdf) viewer.append(frame); + if (evidencePage === pageNumber) scrollPdfEvidenceTargetIntoView(canvas, evidenceOverlay); }; const openInlinePdfResourceTab = async (entry, input) => { diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js index c6ff7531..4c38cfc2 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js @@ -41,6 +41,8 @@ export function createSidebarPageAiRenderRuntime(context) { pageAiSessionAgentFilterValue, pageAiSessionPreviewText, pageAiSessionStorageLabel, + pageAiSessionSelected, + pageAiSelectedSessionCount, pageAiSkillEnabled, pageAiSkillGroupCollapsed, pageAiSkillListEntries, @@ -601,6 +603,15 @@ export function createSidebarPageAiRenderRuntime(context) { sessionError.textContent = pageUiState.pageAiSessionError || ''; sessionError.hidden = !pageUiState.pageAiSessionError; } + var selectedSessionCount = pageAiSelectedSessionCount(); + var selectedSessionToolbar = drawer.querySelector('[data-page-ai-history-selection-toolbar]'); + if (selectedSessionToolbar instanceof HTMLElement) { + selectedSessionToolbar.hidden = selectedSessionCount <= 0; + } + var selectedSessionCountNode = drawer.querySelector('[data-page-ai-history-selection-count]'); + if (selectedSessionCountNode instanceof HTMLElement) { + selectedSessionCountNode.textContent = '已选 ' + String(selectedSessionCount) + ' 个'; + } var sessionSearch = drawer.querySelector('[data-page-ai-session-search]'); if (sessionSearch instanceof HTMLInputElement && documentRef.activeElement !== sessionSearch) { sessionSearch.value = pageUiState.pageAiSessionSearchQuery || ''; @@ -1009,6 +1020,11 @@ export function createSidebarPageAiRenderRuntime(context) { '