feat: add evidence search and stabilize pdf previews
- add document evidence parsing/search/open routes, Hermes tool wiring, local index settings/status, and the document-evidence skill plus design notes - fix PDF resource tabs by rendering PDFs inline with pdf.js canvases instead of iframe preview pages, release PDF documents on close, and document the fourth-PDF stall bug - keep PDF preview at 2x rendering while removing the previous lazy-load/placeholder direction, and make dev:hot bind loopback defaults externally reachable Verification: - node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js - node scripts/task-dev-hot-plan-test.js - cargo test -p mnote-web --manifest-path rust/Cargo.toml pdf_preview_page_does_not_render_visible_toolbar - cargo test -p mnote-web --manifest-path rust/Cargo.toml document_shell_returns_page_aggregate_snapshot - cargo build -p mnote-web --manifest-path rust/Cargo.toml - browser smoke: sequentially opened the four tea_seed_oil_cosmetic PDFs; fourth PDF rendered 15/15 canvases, iframeCount=0, browser errors=0
This commit is contained in:
@@ -784,6 +784,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
console.warn('mnote mindmap resource tab unmount failed', error);
|
||||
}
|
||||
}
|
||||
releaseInlinePdfResource(entry);
|
||||
if (entry.tab instanceof HTMLElement) entry.tab.remove();
|
||||
if (entry.panel instanceof HTMLElement) entry.panel.remove();
|
||||
resourceTabRegistry.delete(key);
|
||||
@@ -883,6 +884,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
|
||||
const releaseResourceTabEntryRuntime = (entry) => {
|
||||
if (!entry) return;
|
||||
releaseInlinePdfResource(entry);
|
||||
if (entry.view) {
|
||||
unmountEditorViewBinding(entry.view, { releaseSession: true });
|
||||
entry.view = null;
|
||||
@@ -926,6 +928,144 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
const normalizeEvidenceBBox = (value) => {
|
||||
if (!value) return null;
|
||||
if (Array.isArray(value) && value.length >= 4) {
|
||||
const values = value.slice(0, 4).map((item) => Number(item));
|
||||
return values.every(Number.isFinite) ? { x0: values[0], y0: values[1], x1: values[2], y1: values[3] } : null;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
const bbox = {
|
||||
x0: Number(value.x0),
|
||||
y0: Number(value.y0),
|
||||
x1: Number(value.x1),
|
||||
y1: Number(value.y1),
|
||||
};
|
||||
return Object.values(bbox).every(Number.isFinite) ? bbox : null;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parts = value.split(',').map((item) => Number(item.trim()));
|
||||
return parts.length >= 4 && parts.slice(0, 4).every(Number.isFinite)
|
||||
? { x0: parts[0], y0: parts[1], x1: parts[2], y1: parts[3] }
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const normalizeEvidenceLocatorInput = (input = {}) => {
|
||||
const locator = input.evidenceLocator && typeof input.evidenceLocator === 'object'
|
||||
? input.evidenceLocator
|
||||
: input.locator && typeof input.locator === 'object'
|
||||
? input.locator
|
||||
: null;
|
||||
const params = locator?.openAction?.params && typeof locator.openAction.params === 'object' ? locator.openAction.params : {};
|
||||
const page = Number(input.page ?? locator?.page ?? params.page);
|
||||
const bbox = normalizeEvidenceBBox(input.bbox ?? locator?.bbox ?? params.bbox);
|
||||
const sourceMapPath = String(input.sourceMapPath || locator?.sourceMapPath || params.sourceMapPath || '').trim();
|
||||
const blockId = String(input.blockId || locator?.blockId || params.blockId || '').trim();
|
||||
const lineRange = input.lineRange || locator?.lineRange || params.lineRange || null;
|
||||
const charRange = input.charRange || locator?.charRange || params.charRange || null;
|
||||
if (!locator && !Number.isFinite(page) && !bbox && !sourceMapPath && !blockId && !lineRange && !charRange) return null;
|
||||
return {
|
||||
schema: 'mnote.evidence_locator.v1',
|
||||
...(locator || {}),
|
||||
page: Number.isFinite(page) ? page : null,
|
||||
bbox,
|
||||
sourceMapPath,
|
||||
blockId,
|
||||
lineRange,
|
||||
charRange,
|
||||
};
|
||||
};
|
||||
|
||||
const evidenceBBoxParam = (bbox) => {
|
||||
const normalized = normalizeEvidenceBBox(bbox);
|
||||
return normalized ? [normalized.x0, normalized.y0, normalized.x1, normalized.y1].join(',') : '';
|
||||
};
|
||||
|
||||
const applyEvidenceLocatorToFrame = (frame, locator) => {
|
||||
if (!(frame instanceof HTMLIFrameElement) || !locator) return;
|
||||
try {
|
||||
const url = new URL(frame.getAttribute('src') || frame.src || '', window.location.origin);
|
||||
if (Number.isFinite(Number(locator.page))) url.searchParams.set('page', String(Number(locator.page)));
|
||||
const bbox = evidenceBBoxParam(locator.bbox);
|
||||
if (bbox) url.searchParams.set('bbox', bbox);
|
||||
if (locator.sourceMapPath) url.searchParams.set('sourceMapPath', String(locator.sourceMapPath));
|
||||
if (locator.blockId) url.searchParams.set('blockId', String(locator.blockId));
|
||||
frame.src = url.toString();
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
const applyEvidenceLocatorToImagePanel = (entry, locator) => {
|
||||
if (!(entry?.panel instanceof HTMLElement) || !locator) return;
|
||||
const bbox = normalizeEvidenceBBox(locator.bbox);
|
||||
let overlay = entry.panel.querySelector('[data-mnote-evidence-bbox-highlight]');
|
||||
if (!bbox) {
|
||||
if (overlay instanceof HTMLElement) overlay.hidden = true;
|
||||
return;
|
||||
}
|
||||
if (!(overlay instanceof HTMLElement)) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.className = 'mnote-resource-tab-bbox-highlight';
|
||||
overlay.setAttribute('data-mnote-evidence-bbox-highlight', 'true');
|
||||
entry.panel.append(overlay);
|
||||
}
|
||||
overlay.hidden = false;
|
||||
overlay.style.left = `${Math.max(0, bbox.x0)}px`;
|
||||
overlay.style.top = `${Math.max(0, bbox.y0)}px`;
|
||||
overlay.style.width = `${Math.max(1, bbox.x1 - bbox.x0)}px`;
|
||||
overlay.style.height = `${Math.max(1, bbox.y1 - bbox.y0)}px`;
|
||||
};
|
||||
|
||||
const applyEvidenceLocatorToTextPanel = (entry, locator) => {
|
||||
if (!(entry?.panel instanceof HTMLElement) || !locator?.blockId) return;
|
||||
const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
|
||||
if (!(root instanceof HTMLElement)) return;
|
||||
const selector = `[data-block-id="${cssSafe(locator.blockId)}"]`;
|
||||
const target = root.querySelector(selector);
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
root.querySelectorAll('[data-mnote-evidence-text-highlight="true"]').forEach((node) => {
|
||||
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-text-highlight');
|
||||
});
|
||||
target.setAttribute('data-mnote-evidence-text-highlight', 'true');
|
||||
target.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
};
|
||||
|
||||
const applyEvidenceLocatorToInlinePdfPanel = (entry, locator) => {
|
||||
if (!(entry?.panel instanceof HTMLElement) || !locator) return;
|
||||
const pageNumber = Number(locator.page || 0);
|
||||
if (!Number.isFinite(pageNumber) || pageNumber <= 0) return;
|
||||
const canvas = entry.panel.querySelector(`canvas.mnote-pdf-page[data-page-number="${pageNumber}"]`);
|
||||
if (!(canvas instanceof HTMLCanvasElement)) return;
|
||||
entry.panel.querySelectorAll('canvas.mnote-pdf-page[data-mnote-evidence-page="true"]').forEach((node) => {
|
||||
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-page');
|
||||
});
|
||||
canvas.setAttribute('data-mnote-evidence-page', 'true');
|
||||
canvas.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
};
|
||||
|
||||
const applyEvidenceLocatorToEntry = (entry, input = {}) => {
|
||||
if (!(entry?.panel instanceof HTMLElement)) return;
|
||||
const locator = normalizeEvidenceLocatorInput(input);
|
||||
if (!locator) return;
|
||||
entry.evidenceLocator = locator;
|
||||
entry.panel.setAttribute('data-mnote-evidence-locator', JSON.stringify(locator));
|
||||
entry.panel.setAttribute('data-mnote-evidence-open', 'true');
|
||||
if (Number.isFinite(Number(locator.page))) entry.panel.setAttribute('data-mnote-evidence-page', String(Number(locator.page)));
|
||||
if (locator.blockId) entry.panel.setAttribute('data-mnote-evidence-block-id', String(locator.blockId));
|
||||
if (locator.sourceMapPath) entry.panel.setAttribute('data-mnote-evidence-source-map-path', String(locator.sourceMapPath));
|
||||
const bbox = evidenceBBoxParam(locator.bbox);
|
||||
if (bbox) entry.panel.setAttribute('data-mnote-evidence-bbox', bbox);
|
||||
const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
|
||||
if (frame instanceof HTMLIFrameElement) applyEvidenceLocatorToFrame(frame, locator);
|
||||
if (entry.kind === 'image') applyEvidenceLocatorToImagePanel(entry, locator);
|
||||
if (entry.kind === 'pdf') applyEvidenceLocatorToInlinePdfPanel(entry, locator);
|
||||
if (entry.kind === 'markdown' || entry.kind === 'text' || entry.kind === 'code') {
|
||||
window.setTimeout(() => applyEvidenceLocatorToTextPanel(entry, locator), 80);
|
||||
window.setTimeout(() => applyEvidenceLocatorToTextPanel(entry, locator), 450);
|
||||
}
|
||||
};
|
||||
|
||||
const renderPassiveResourceMissing = (entry, message) => {
|
||||
if (!(entry?.panel instanceof HTMLElement)) return;
|
||||
entry.panel.setAttribute('data-mnote-resource-missing', 'true');
|
||||
@@ -945,6 +1085,10 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
|
||||
if (frame instanceof HTMLIFrameElement) {
|
||||
frame.src = withResourceReloadToken(frame.getAttribute('src') || frame.src || '');
|
||||
return;
|
||||
}
|
||||
if (entry.kind === 'pdf' && entry.inlinePdfSourceHref) {
|
||||
void openPassiveResourceTab(entry, { ...(entry.lastPassiveInput || {}), href: withResourceReloadToken(entry.inlinePdfSourceHref) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -986,6 +1130,115 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const releaseInlinePdfResource = (entry) => {
|
||||
if (!entry) return;
|
||||
const pdf = entry.inlinePdfDocument;
|
||||
entry.inlinePdfDocument = null;
|
||||
entry.inlinePdfRenderToken = null;
|
||||
if (!pdf) return;
|
||||
try {
|
||||
void pdf.destroy();
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
const pdfFileUrlFromPreviewHref = (href) => {
|
||||
const value = String(href || '').trim();
|
||||
if (!value) return '';
|
||||
try {
|
||||
const url = new URL(value, window.location.origin);
|
||||
return String(url.searchParams.get('fileUrl') || value).trim();
|
||||
} catch (_) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const renderInlinePdfPage = async (entry, viewer, pdf, pageNumber, evidenceLocator) => {
|
||||
const page = await pdf.getPage(pageNumber);
|
||||
const baseViewport = page.getViewport({ scale: 1 });
|
||||
const availableWidth = Math.max(280, viewer.clientWidth - 20);
|
||||
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 canvas = document.createElement('canvas');
|
||||
canvas.className = 'mnote-pdf-page';
|
||||
canvas.setAttribute('data-page-number', String(pageNumber));
|
||||
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.background = '#fff';
|
||||
canvas.style.border = '1px solid #d8d8d2';
|
||||
canvas.style.boxShadow = '0 2px 10px rgba(25, 25, 22, .08)';
|
||||
const context = canvas.getContext('2d', { alpha: false });
|
||||
if (!context) return;
|
||||
await page.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null,
|
||||
}).promise;
|
||||
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);
|
||||
};
|
||||
|
||||
const openInlinePdfResourceTab = async (entry, input) => {
|
||||
const href = String(input.officeUrl || input.href || '').trim();
|
||||
const fileUrl = pdfFileUrlFromPreviewHref(href);
|
||||
if (!(entry?.panel instanceof HTMLElement) || !fileUrl) return false;
|
||||
releaseInlinePdfResource(entry);
|
||||
const renderToken = {};
|
||||
entry.passiveFrameSrc = href;
|
||||
entry.inlinePdfSourceHref = href;
|
||||
entry.inlinePdfRenderToken = renderToken;
|
||||
entry.lastPassiveInput = { ...input };
|
||||
entry.panel.replaceChildren();
|
||||
const viewer = document.createElement('div');
|
||||
viewer.className = 'mnote-pdf-viewer';
|
||||
viewer.setAttribute('data-mnote-inline-pdf-viewer', 'true');
|
||||
viewer.style.width = '100%';
|
||||
viewer.style.maxWidth = '1180px';
|
||||
viewer.style.margin = '0 auto';
|
||||
viewer.style.padding = '8px 12px 28px';
|
||||
entry.panel.append(viewer);
|
||||
const pdfjsLib = await import('/api/pdfjs/pdf.mjs');
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = '/api/pdfjs/pdf.worker.mjs';
|
||||
const sameOrigin = fileUrl.startsWith('/') || fileUrl.startsWith(window.location.origin);
|
||||
const pdf = await pdfjsLib.getDocument({ url: fileUrl, withCredentials: sameOrigin, disableWorker: true }).promise;
|
||||
entry.inlinePdfDocument = pdf;
|
||||
const total = Number(pdf.numPages || 0);
|
||||
viewer.setAttribute('data-mnote-pdf-status', `0 / ${total}`);
|
||||
const evidenceLocator = normalizeEvidenceLocatorInput(input);
|
||||
for (let pageNumber = 1; pageNumber <= total; pageNumber += 1) {
|
||||
if (entry.inlinePdfDocument !== pdf || entry.inlinePdfRenderToken !== renderToken) return true;
|
||||
await renderInlinePdfPage(entry, viewer, pdf, pageNumber, evidenceLocator);
|
||||
viewer.setAttribute('data-mnote-pdf-status', `${pageNumber} / ${total}`);
|
||||
}
|
||||
installPassiveResourceWatch(entry);
|
||||
return true;
|
||||
};
|
||||
|
||||
const isLocalOcrSourceEntry = (entry) => {
|
||||
if (!entry || String(entry.sourceKind || '').trim() !== 'local_folder') return false;
|
||||
if (!String(entry.rootUri || '').trim() || !String(entry.path || '').trim()) return false;
|
||||
@@ -1136,7 +1389,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
if (!(toggle instanceof HTMLButtonElement)) return null;
|
||||
if (toggle.getAttribute('data-mnote-local-ocr-bound') === 'true') return toggle;
|
||||
toggle.setAttribute('data-mnote-local-ocr-bound', 'true');
|
||||
toggle.addEventListener('click', () => {
|
||||
toggle.addEventListener('click', (event) => {
|
||||
if (toggle.getAttribute('data-mnote-action') === 'open-ocr-settings') {
|
||||
event.preventDefault();
|
||||
window.dispatchEvent(new CustomEvent('mnote:open-local-ocr-settings'));
|
||||
return;
|
||||
}
|
||||
void runManualLocalOcrForActiveTarget(toggle).catch((error) => {
|
||||
console.warn('mnote local OCR 手动入口失败', error);
|
||||
});
|
||||
@@ -1187,10 +1445,10 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
toggle = document.createElement('button');
|
||||
toggle.type = 'button';
|
||||
toggle.className = 'wolai-icon-button mnote-local-ocr-task-toggle';
|
||||
toggle.setAttribute('title', 'OCR 任务');
|
||||
toggle.setAttribute('aria-label', 'OCR 任务');
|
||||
toggle.setAttribute('title', 'OCR 设置');
|
||||
toggle.setAttribute('aria-label', 'OCR 设置');
|
||||
toggle.setAttribute('data-testid', 'mnote-local-ocr-task-toggle');
|
||||
toggle.setAttribute('data-mnote-action', 'toggle-ocr-tasks');
|
||||
toggle.setAttribute('data-mnote-action', 'open-ocr-settings');
|
||||
toggle.innerHTML = '<span class="material-symbols-outlined" data-icon="document_scanner" aria-hidden="true"></span><span class="mnote-local-ocr-task-badge" data-mnote-local-ocr-task-count hidden>0</span>';
|
||||
if (actions instanceof HTMLElement) actions.appendChild(toggle);
|
||||
else document.body.appendChild(toggle);
|
||||
@@ -1276,7 +1534,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const runningCount = jobs.filter((job) => !['done', 'failed', 'stale'].includes(String(job?.status || ''))).length;
|
||||
const toggle = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
|
||||
if (toggle instanceof HTMLButtonElement) {
|
||||
const label = runningCount > 0 ? `${runningCount} 个 OCR 处理中` : (jobs.length > 0 ? `${jobs.length} 个 OCR 任务` : 'OCR 任务');
|
||||
const label = runningCount > 0 ? `${runningCount} 个 OCR 处理中,打开 OCR 设置` : (jobs.length > 0 ? `${jobs.length} 个 OCR 任务,打开 OCR 设置` : 'OCR 设置');
|
||||
toggle.setAttribute('title', label);
|
||||
toggle.setAttribute('aria-label', label);
|
||||
toggle.setAttribute('aria-expanded', localOcrTaskState.drawerOpen ? 'true' : 'false');
|
||||
@@ -1595,6 +1853,22 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
return jobs;
|
||||
};
|
||||
|
||||
window.addEventListener('mnote:local-ocr-settings-action', (event) => {
|
||||
const action = String(event?.detail?.action || '').trim();
|
||||
if (action === 'run-active') {
|
||||
const toggle = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
|
||||
void runManualLocalOcrForActiveTarget(toggle instanceof HTMLButtonElement ? toggle : null).catch((error) => {
|
||||
console.warn('mnote local OCR 设置入口识别失败', error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'tasks') {
|
||||
ensureLocalOcrTaskDock();
|
||||
localOcrTaskState.drawerOpen = true;
|
||||
renderLocalOcrTaskDock();
|
||||
}
|
||||
});
|
||||
|
||||
const renderLocalOcrToolbar = (entry) => {
|
||||
if (!isLocalOcrSourceEntry(entry) || !(entry.panel instanceof HTMLElement)) return;
|
||||
const toolbar = entry.panel.querySelector('[data-testid="mnote-local-ocr-toolbar"]');
|
||||
@@ -1765,15 +2039,16 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const openPassiveResourceTab = (entry, input) => {
|
||||
const openPassiveResourceTab = async (entry, input) => {
|
||||
const href = String(input.officeUrl || input.href || '').trim();
|
||||
if (entry.kind === 'image') {
|
||||
entry.panel.innerHTML = '<img class="mnote-resource-tab-image" alt="">';
|
||||
entry.panel.innerHTML = '<div class="mnote-resource-tab-image-shell"><img class="mnote-resource-tab-image" alt=""><div class="mnote-resource-tab-bbox-highlight" data-mnote-evidence-bbox-highlight="true" hidden></div></div>';
|
||||
const img = entry.panel.querySelector('img');
|
||||
if (img instanceof HTMLImageElement) {
|
||||
img.src = href;
|
||||
img.alt = entry.title;
|
||||
}
|
||||
applyEvidenceLocatorToEntry(entry, input);
|
||||
ensureLocalOcrTaskDock();
|
||||
ensureLocalOcrTaskEvents(entry.rootUri);
|
||||
void loadLocalOcrJobs(entry.rootUri).catch(() => undefined);
|
||||
@@ -1781,6 +2056,18 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
installPassiveResourceWatch(entry);
|
||||
return;
|
||||
}
|
||||
if (entry.kind === 'pdf') {
|
||||
await openInlinePdfResourceTab(entry, input);
|
||||
applyEvidenceLocatorToEntry(entry, input);
|
||||
if (isLocalOcrSourceEntry(entry)) {
|
||||
ensureLocalOcrTaskDock();
|
||||
ensureLocalOcrTaskEvents(entry.rootUri);
|
||||
void loadLocalOcrJobs(entry.rootUri).catch(() => undefined);
|
||||
void maybeAutoCreateLocalOcrJob(entry).catch((error) => console.warn('mnote local OCR 自动任务失败', error));
|
||||
}
|
||||
installPassiveResourceWatch(entry);
|
||||
return;
|
||||
}
|
||||
entry.panel.innerHTML = '<iframe class="mnote-resource-tab-frame" title=""></iframe>';
|
||||
const frame = entry.panel.querySelector('iframe');
|
||||
if (frame instanceof HTMLIFrameElement) {
|
||||
@@ -1792,7 +2079,9 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
}, { once: true });
|
||||
}
|
||||
frame.src = href;
|
||||
entry.passiveFrameSrc = href;
|
||||
}
|
||||
applyEvidenceLocatorToEntry(entry, input);
|
||||
if (isLocalOcrSourceEntry(entry)) {
|
||||
ensureLocalOcrTaskDock();
|
||||
ensureLocalOcrTaskEvents(entry.rootUri);
|
||||
@@ -1802,6 +2091,16 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
installPassiveResourceWatch(entry);
|
||||
};
|
||||
|
||||
const refreshExistingPdfResourceTab = async (entry, input) => {
|
||||
if (!entry || entry.kind !== 'pdf') return false;
|
||||
const nextHref = String(input.officeUrl || input.href || '').trim();
|
||||
if (!nextHref) return false;
|
||||
if (nextHref !== String(entry.inlinePdfSourceHref || entry.passiveFrameSrc || '').trim()) {
|
||||
await openPassiveResourceTab(entry, input);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const refreshExistingOfficeResourceTab = (entry, input) => {
|
||||
if (!entry || entry.kind !== 'office') return false;
|
||||
const nextHref = String(input.officeUrl || input.href || '').trim();
|
||||
@@ -1810,7 +2109,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const currentHref = frame instanceof HTMLIFrameElement
|
||||
? String(frame.getAttribute('src') || frame.src || '').trim()
|
||||
: '';
|
||||
if (currentHref !== nextHref) openPassiveResourceTab(entry, input);
|
||||
if (currentHref !== nextHref) void openPassiveResourceTab(entry, input);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1883,6 +2182,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const objectIdentity = String(input.objectIdentity || input.assetId || input.href || '').trim();
|
||||
if (!objectIdentity) return false;
|
||||
const registryKey = resourceTabRegistryKey(paneRole, objectIdentity);
|
||||
const requestedKind = normalizeResourceTabKind(input);
|
||||
if (paneRole === 'secondary') {
|
||||
resourceTabRegistry.forEach((entry, key) => {
|
||||
if (normalizePaneRole(entry.paneRole) !== paneRole) return;
|
||||
@@ -1896,8 +2196,10 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
}
|
||||
const existing = resourceTabRegistry.get(registryKey);
|
||||
if (existing) {
|
||||
refreshExistingOfficeResourceTab(existing, input);
|
||||
activateMainEditorTab(registryKey, paneRole);
|
||||
await refreshExistingPdfResourceTab(existing, input);
|
||||
refreshExistingOfficeResourceTab(existing, input);
|
||||
applyEvidenceLocatorToEntry(existing, input);
|
||||
return true;
|
||||
}
|
||||
const entry = createResourceTabDom({ ...input, objectIdentity, paneRole });
|
||||
@@ -1910,8 +2212,9 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
} else if (entry.kind === 'markdown' || entry.kind === 'text' || entry.kind === 'code') {
|
||||
await openTiptapResourceTab(entry, input);
|
||||
} else {
|
||||
openPassiveResourceTab(entry, input);
|
||||
await openPassiveResourceTab(entry, input);
|
||||
}
|
||||
applyEvidenceLocatorToEntry(entry, input);
|
||||
activateMainEditorTab(registryKey, paneRole);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user