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:
@@ -253,6 +253,29 @@ import {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
const cssSafe = (value) => {
|
||||
const text = String(value || '');
|
||||
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text);
|
||||
return text.replace(/["\\]/g, '\\$&');
|
||||
};
|
||||
const applyDocumentEvidenceLocatorFromUrl = (root) => {
|
||||
if (!(root instanceof HTMLElement)) return;
|
||||
const url = currentUrl();
|
||||
const blockId = String(url.searchParams.get('blockId') || url.searchParams.get('evidenceBlockId') || '').trim();
|
||||
const lineRange = String(url.searchParams.get('lineRange') || '').trim();
|
||||
if (!blockId && !lineRange) return;
|
||||
root.setAttribute('data-mnote-evidence-open', 'true');
|
||||
if (blockId) root.setAttribute('data-mnote-evidence-block-id', blockId);
|
||||
if (lineRange) root.setAttribute('data-mnote-evidence-line-range', lineRange);
|
||||
const target = blockId ? root.querySelector(`[data-block-id="${cssSafe(blockId)}"]`) : null;
|
||||
if (target instanceof HTMLElement) {
|
||||
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 restoreResourceTabInputFromUrl = () => {
|
||||
const url = currentUrl();
|
||||
@@ -785,6 +808,7 @@ import {
|
||||
if (session.projectionSource) runtimeDescriptor.root.setAttribute('data-mnote-projection-source', session.projectionSource);
|
||||
if (session.blockProjectionVersion) runtimeDescriptor.root.setAttribute('data-mnote-block-projection-version', session.blockProjectionVersion);
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
applyDocumentEvidenceLocatorFromUrl(runtimeDescriptor.root);
|
||||
};
|
||||
view.onError = (event) => {
|
||||
const payload = normalizeEnvelopePayload(event);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -74,6 +74,23 @@ export const mergeTiptapMarks = (...groups) => {
|
||||
});
|
||||
};
|
||||
|
||||
const legacyBlockAttrs = (block) => (
|
||||
{
|
||||
...(block?.attrs && typeof block.attrs === 'object' ? block.attrs : {}),
|
||||
...(block?.props && typeof block.props === 'object' ? block.props : {}),
|
||||
}
|
||||
);
|
||||
|
||||
const legacyBlockAttr = (block, ...keys) => {
|
||||
const props = block?.props && typeof block.props === 'object' ? block.props : {};
|
||||
const attrs = block?.attrs && typeof block.attrs === 'object' ? block.attrs : {};
|
||||
for (const key of keys) {
|
||||
if (props[key] !== undefined) return props[key];
|
||||
if (attrs[key] !== undefined) return attrs[key];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const legacyInlineContentToTiptap = (value) => {
|
||||
if (typeof value === 'string') return value ? [{ type: 'text', text: value }] : [];
|
||||
if (Array.isArray(value)) return value.flatMap(legacyInlineContentToTiptap);
|
||||
@@ -102,16 +119,17 @@ export const legacyInlineContentToTiptap = (value) => {
|
||||
|
||||
export const legacyBlockToTiptap = (block, index = 0) => {
|
||||
const type = typeof block?.type === 'string' ? block.type : 'paragraph';
|
||||
const blockAttrs = legacyBlockAttrs(block);
|
||||
const blockId = typeof block?.id === 'string' && block.id.trim()
|
||||
? block.id.trim()
|
||||
: typeof block?.blockId === 'string' && block.blockId.trim()
|
||||
? block.blockId.trim()
|
||||
: `block-${index + 1}`;
|
||||
const content = legacyInlineContentToTiptap(block?.content ?? block?.contentNodes);
|
||||
const textAlign = typeof block?.props?.textAlign === 'string'
|
||||
? block.props.textAlign
|
||||
: typeof block?.props?.text_align === 'string'
|
||||
? block.props.text_align
|
||||
const textAlign = typeof legacyBlockAttr(block, 'textAlign') === 'string'
|
||||
? legacyBlockAttr(block, 'textAlign')
|
||||
: typeof legacyBlockAttr(block, 'text_align') === 'string'
|
||||
? legacyBlockAttr(block, 'text_align')
|
||||
: undefined;
|
||||
const withTextAlign = (attrs = {}) => textAlign ? { ...attrs, textAlign } : attrs;
|
||||
const nestedChildren = Array.isArray(block?.children)
|
||||
@@ -130,24 +148,24 @@ export const legacyBlockToTiptap = (block, index = 0) => {
|
||||
}],
|
||||
});
|
||||
if (type === 'heading') {
|
||||
const level = Number(block?.props?.level || block?.level || 1) || 1;
|
||||
const collapsed = typeof block?.props?.collapsed === 'boolean' ? { collapsed: block.props.collapsed } : {};
|
||||
const level = Number(legacyBlockAttr(block, 'level', 'headingLevel') || block?.level || 1) || 1;
|
||||
const collapsed = typeof legacyBlockAttr(block, 'collapsed') === 'boolean' ? { collapsed: legacyBlockAttr(block, 'collapsed') } : {};
|
||||
return { type: 'heading', attrs: withTextAlign({ blockId, level: Math.max(1, Math.min(6, level)), ...collapsed }), content };
|
||||
}
|
||||
if (type === 'bulletListItem' || type === 'bullet_list_item') return withListChildren('listItem', 'bulletList');
|
||||
if (type === 'numberedListItem' || type === 'numbered_list_item') return withListChildren('listItem', 'orderedList');
|
||||
if (type === 'checkListItem' || type === 'advancedTodo' || type === 'todo') {
|
||||
return withListChildren('taskItem', 'taskList', { checked: Boolean(block?.props?.checked) });
|
||||
return withListChildren('taskItem', 'taskList', { checked: legacyBlockAttr(block, 'checked') === true });
|
||||
}
|
||||
if (type === 'quote' || type === 'blockquote') {
|
||||
return { type: 'blockquote', attrs: withTextAlign({ blockId }), content: [{ type: 'paragraph', attrs: withTextAlign({ blockId }), content }] };
|
||||
}
|
||||
if (type === 'codeBlock' || type === 'code_block') {
|
||||
return { type: 'codeBlock', attrs: withTextAlign({ blockId, language: block?.props?.language || null }), content };
|
||||
return { type: 'codeBlock', attrs: withTextAlign({ blockId, language: legacyBlockAttr(block, 'language') || null }), content };
|
||||
}
|
||||
if (type === 'divider' || type === 'horizontalRule' || type === 'horizontal_rule') return { type: 'horizontalRule', attrs: { blockId } };
|
||||
if (type === 'mindmap') {
|
||||
const attrs = block?.attrs && typeof block.attrs === 'object' ? block.attrs : null;
|
||||
const attrs = blockAttrs;
|
||||
const data = block?.props?.data && typeof block.props.data === 'object' ? block.props.data : null;
|
||||
const mindmapId = firstNonEmptyText(
|
||||
block?.props?.mindmapId,
|
||||
|
||||
@@ -1582,7 +1582,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
var objectKind = row instanceof HTMLElement ? String(row.getAttribute('data-object-kind') || '').trim() : '';
|
||||
var title = rowTitle(row).toLowerCase();
|
||||
if (objectKind === 'mindmap' || iconKind === 'mindmap') return 'mindmap';
|
||||
if (objectKind === 'table' || iconKind === 'table' || iconKind === 'luckysheet' || title.indexOf('.luckysheet') >= 0) return 'table';
|
||||
if (objectKind === 'table' || iconKind === 'table') return 'table';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
@@ -1905,7 +1905,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true');
|
||||
window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'bulk-delete', failed: 0, count: total } }));
|
||||
recordFileTreeActionStatus('archived', { count: total, undo: 'trash-modal', batchId: batchId });
|
||||
if (currentSourceKind() !== 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -304,7 +304,14 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
documentId: String(input && input.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(input && input.workspaceId || resolveWorkspaceId(document.body) || '').trim(),
|
||||
workspacePath: workspacePath,
|
||||
paneRole: String(input && input.paneRole || 'primary').trim() || 'primary'
|
||||
paneRole: String(input && input.paneRole || 'primary').trim() || 'primary',
|
||||
evidenceLocator: input && input.evidenceLocator && typeof input.evidenceLocator === 'object' ? input.evidenceLocator : null,
|
||||
page: input && input.page,
|
||||
bbox: input && input.bbox,
|
||||
sourceMapPath: String(input && input.sourceMapPath || '').trim(),
|
||||
blockId: String(input && input.blockId || '').trim(),
|
||||
lineRange: input && input.lineRange,
|
||||
charRange: input && input.charRange
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -324,6 +324,21 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
trigger.setAttribute('aria-expanded', pageUiState.pageSettingsOpen ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function updateStandaloneSettingsTriggerState() {
|
||||
var indexTrigger = document.querySelector('[data-testid="mnote-local-index-settings-toggle"]');
|
||||
var indexOpen = isLocalIndexSettingsOpen();
|
||||
if (indexTrigger instanceof HTMLElement) {
|
||||
indexTrigger.setAttribute('data-state', indexOpen ? 'open' : 'closed');
|
||||
indexTrigger.setAttribute('aria-expanded', indexOpen ? 'true' : 'false');
|
||||
}
|
||||
var ocrTrigger = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
|
||||
var ocrOpen = isLocalOcrSettingsOpen();
|
||||
if (ocrTrigger instanceof HTMLElement) {
|
||||
ocrTrigger.setAttribute('data-state', ocrOpen ? 'open' : 'closed');
|
||||
ocrTrigger.setAttribute('aria-expanded', ocrOpen ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
|
||||
function createPageOptionRow(key, type) {
|
||||
var inputType = type || 'checkbox';
|
||||
var supported = pageOptionIsSupported(key);
|
||||
@@ -412,10 +427,6 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
popover.querySelectorAll('[data-global-option-checkbox="showHeadingNumbers"]').forEach(function(input) {
|
||||
input.checked = globalHeadingNumbers;
|
||||
});
|
||||
var localOcrPreferences = currentLocalOcrPreferences();
|
||||
popover.querySelectorAll('[data-local-ocr-option-checkbox="autoEnabled"]').forEach(function(input) {
|
||||
input.checked = localOcrPreferences['localOcr.autoEnabled'] === true;
|
||||
});
|
||||
var preferences = currentPageWidthPreferences();
|
||||
popover.querySelectorAll('[data-page-width-select]').forEach(function(select) {
|
||||
var type = select.getAttribute('data-page-width-select') || '';
|
||||
@@ -430,6 +441,13 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
});
|
||||
}
|
||||
|
||||
function renderLocalOcrOptions(popover) {
|
||||
var localOcrPreferences = currentLocalOcrPreferences();
|
||||
popover.querySelectorAll('[data-local-ocr-option-checkbox="autoEnabled"]').forEach(function(input) {
|
||||
input.checked = localOcrPreferences['localOcr.autoEnabled'] === true;
|
||||
});
|
||||
}
|
||||
|
||||
function createPageFontRow() {
|
||||
return '' +
|
||||
'<label class="wolai-page-setting-row" data-page-setting-row="pageFont">' +
|
||||
@@ -566,7 +584,6 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
'<div class="wolai-page-settings-tabs" data-testid="wolai-page-settings-tabs" role="tablist" aria-label="页面设置分组">' +
|
||||
'<button type="button" class="wolai-page-settings-tab is-active" role="tab" aria-selected="true" data-page-settings-tab="page">页面选项</button>' +
|
||||
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="custom">自定义页面</button>' +
|
||||
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="index">索引</button>' +
|
||||
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="global">全局选项</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-section" data-page-settings-panel="page">' +
|
||||
@@ -583,22 +600,8 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
createPageOptionRow('hideChildPages', 'checkbox') +
|
||||
createPageOptionRow('showBlockRefCount', 'checkbox') +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-section" data-page-settings-panel="index" hidden>' +
|
||||
'<div class="wolai-page-settings-index-panel" data-testid="wolai-page-settings-local-index-panel">' +
|
||||
'<div class="wolai-page-settings-index-status" data-testid="wolai-page-settings-local-index-status"></div>' +
|
||||
'<section class="wolai-page-settings-index-group">' +
|
||||
'<div class="wolai-page-settings-index-title">反链</div>' +
|
||||
'<div class="wolai-page-settings-index-list" data-testid="wolai-page-settings-local-index-backlinks"></div>' +
|
||||
'</section>' +
|
||||
'<section class="wolai-page-settings-index-group">' +
|
||||
'<div class="wolai-page-settings-index-title">标签</div>' +
|
||||
'<div class="wolai-page-settings-index-list" data-testid="wolai-page-settings-local-index-tags"></div>' +
|
||||
'</section>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-section" data-page-settings-panel="global" hidden>' +
|
||||
createGlobalHeadingNumbersRow() +
|
||||
createLocalOcrAutoRow() +
|
||||
createPageWidthRows() +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-actions">' +
|
||||
@@ -614,6 +617,73 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
return popover;
|
||||
}
|
||||
|
||||
function createLocalIndexSettingsPanelHtml() {
|
||||
return '' +
|
||||
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-local-index-settings-panel" role="dialog" aria-modal="false" aria-label="索引设置">' +
|
||||
'<div class="mnote-settings-panel-head">' +
|
||||
'<strong>索引设置</strong>' +
|
||||
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭索引设置">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-index-panel" data-testid="wolai-page-settings-local-index-panel">' +
|
||||
'<div class="wolai-page-settings-index-status" data-testid="wolai-page-settings-local-index-status"></div>' +
|
||||
'<section class="wolai-page-settings-index-group">' +
|
||||
'<div class="wolai-page-settings-index-title">索引范围</div>' +
|
||||
'<div class="wolai-page-settings-index-range-list" data-testid="wolai-page-settings-local-index-ranges"></div>' +
|
||||
'<button type="button" class="wolai-page-settings-index-add" data-local-index-action="add-path">新增范围</button>' +
|
||||
'<div class="wolai-page-settings-index-schedule">' +
|
||||
'<label><span>索引时间</span><select data-testid="wolai-page-settings-local-index-schedule-mode"><option value="daily">每日</option><option value="once">指定日期</option><option value="manual">手动</option></select></label>' +
|
||||
'<label><span>时间</span><input type="time" data-testid="wolai-page-settings-local-index-schedule-time" value="02:00"></label>' +
|
||||
'<label><span>日期</span><input type="date" data-testid="wolai-page-settings-local-index-schedule-date"></label>' +
|
||||
'<label class="wolai-page-settings-index-inline"><input type="checkbox" data-testid="wolai-page-settings-local-index-run-on-change"><span>文档变化时立即索引</span></label>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-index-actions">' +
|
||||
'<button type="button" data-local-index-action="save">保存设置</button>' +
|
||||
'<button type="button" data-local-index-action="refresh">重建索引</button>' +
|
||||
'</div>' +
|
||||
'</section>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function ensureLocalIndexSettingsPopover() {
|
||||
var existing = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
||||
if (existing instanceof HTMLElement) return existing;
|
||||
var popover = document.createElement('div');
|
||||
popover.className = 'wolai-page-settings-popover mnote-local-index-settings-popover';
|
||||
popover.setAttribute('data-testid', 'mnote-local-index-settings-popover');
|
||||
popover.setAttribute('data-mnote-surface', 'local-index-settings');
|
||||
popover.hidden = true;
|
||||
popover.innerHTML = createLocalIndexSettingsPanelHtml();
|
||||
document.body.appendChild(popover);
|
||||
return popover;
|
||||
}
|
||||
|
||||
function ensureLocalOcrSettingsPopover() {
|
||||
var existing = document.querySelector('[data-testid="mnote-local-ocr-settings-popover"]');
|
||||
if (existing instanceof HTMLElement) return existing;
|
||||
var popover = document.createElement('div');
|
||||
popover.className = 'wolai-page-settings-popover mnote-local-ocr-settings-popover';
|
||||
popover.setAttribute('data-testid', 'mnote-local-ocr-settings-popover');
|
||||
popover.setAttribute('data-mnote-surface', 'local-ocr-settings');
|
||||
popover.hidden = true;
|
||||
popover.innerHTML = '' +
|
||||
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-local-ocr-settings-panel" role="dialog" aria-modal="false" aria-label="OCR 设置">' +
|
||||
'<div class="mnote-settings-panel-head">' +
|
||||
'<strong>OCR 设置</strong>' +
|
||||
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭 OCR 设置">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-section">' +
|
||||
createLocalOcrAutoRow() +
|
||||
'<div class="wolai-page-settings-index-actions">' +
|
||||
'<button type="button" data-local-ocr-settings-action="run-active">识别当前资源</button>' +
|
||||
'<button type="button" data-local-ocr-settings-action="tasks">查看 OCR 任务</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(popover);
|
||||
return popover;
|
||||
}
|
||||
|
||||
function pageSettingsLocalIndexScopeKey() {
|
||||
return [
|
||||
currentSourceKind(),
|
||||
@@ -624,79 +694,124 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
}
|
||||
|
||||
function pageSettingsLocalIndexIsAvailable() {
|
||||
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri()) && Boolean(currentDocumentId());
|
||||
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
|
||||
}
|
||||
|
||||
function pageSettingsLocalIndexEmpty(message) {
|
||||
return '<div class="wolai-page-settings-index-empty">' + escapeHtml(message) + '</div>';
|
||||
}
|
||||
|
||||
function renderPageSettingsLocalIndexList(items, kind) {
|
||||
var rows = Array.isArray(items) ? items : [];
|
||||
if (!rows.length) {
|
||||
return pageSettingsLocalIndexEmpty(kind === 'backlinks' ? '暂无反链' : '暂无标签');
|
||||
}
|
||||
if (kind === 'backlinks') {
|
||||
return rows.slice(0, 12).map(function(item) {
|
||||
var title = searchText(item && item.title) || searchText(item && item.path) || '未命名页面';
|
||||
var path = searchText(item && item.path);
|
||||
var snippet = searchText(item && item.snippet);
|
||||
return '' +
|
||||
'<div class="wolai-page-settings-index-row">' +
|
||||
'<div class="wolai-page-settings-index-row-title">' + escapeHtml(title) + '</div>' +
|
||||
(path ? '<div class="wolai-page-settings-index-row-meta">' + escapeHtml(path) + '</div>' : '') +
|
||||
(snippet ? '<div class="wolai-page-settings-index-row-snippet">' + escapeHtml(snippet) + '</div>' : '') +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
return rows.slice(0, 16).map(function(item) {
|
||||
var tag = searchText(item && item.tag) || 'untagged';
|
||||
var count = Number(item && item.count || 0);
|
||||
return '' +
|
||||
'<div class="wolai-page-settings-index-row is-tag">' +
|
||||
'<div class="wolai-page-settings-index-row-title">#' + escapeHtml(tag) + '</div>' +
|
||||
'<div class="wolai-page-settings-index-row-meta">' + count + ' 个页面</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderPageSettingsLocalIndex(popover) {
|
||||
popover = popover || ensurePageSettingsPopover();
|
||||
popover = popover || ensureLocalIndexSettingsPopover();
|
||||
var statusNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
|
||||
var backlinksNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-backlinks"]');
|
||||
var tagsNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-tags"]');
|
||||
if (!(statusNode instanceof HTMLElement) || !(backlinksNode instanceof HTMLElement) || !(tagsNode instanceof HTMLElement)) return;
|
||||
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
|
||||
var scheduleModeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
|
||||
var scheduleTimeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
|
||||
var scheduleDateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
|
||||
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
|
||||
if (!(statusNode instanceof HTMLElement)) return;
|
||||
|
||||
if (!pageSettingsLocalIndexIsAvailable()) {
|
||||
statusNode.textContent = '本地索引仅在本地工作区页面可用';
|
||||
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('未连接本地 root');
|
||||
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('未连接本地 root');
|
||||
statusNode.textContent = '本地索引仅在本地工作区可用';
|
||||
renderLocalIndexRangeRows(popover, [], 'fault', true, {});
|
||||
setLocalIndexScheduleControlsDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var summary = pageUiState.localIndexSummary || {};
|
||||
if (summary.loading) {
|
||||
statusNode.textContent = '正在读取本地索引...';
|
||||
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('加载中');
|
||||
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('加载中');
|
||||
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'indexing', true, summary.status || {});
|
||||
setLocalIndexScheduleControlsDisabled(true);
|
||||
return;
|
||||
}
|
||||
if (summary.error) {
|
||||
statusNode.textContent = '本地索引读取失败';
|
||||
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty(summary.error);
|
||||
tagsNode.innerHTML = pageSettingsLocalIndexEmpty(summary.error);
|
||||
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'fault', false, summary.status || {});
|
||||
setLocalIndexScheduleControlsDisabled(false);
|
||||
return;
|
||||
}
|
||||
if (summary.scopeKey !== pageSettingsLocalIndexScopeKey()) {
|
||||
statusNode.textContent = '切换到索引页签后读取本地索引';
|
||||
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('等待读取');
|
||||
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('等待读取');
|
||||
statusNode.textContent = '打开索引设置后读取本地索引';
|
||||
renderLocalIndexRangeRows(popover, [], 'indexing', true, {});
|
||||
setLocalIndexScheduleControlsDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
statusNode.textContent = '来自当前授权 root 的 .mnote/index/search-index.json';
|
||||
backlinksNode.innerHTML = renderPageSettingsLocalIndexList(summary.backlinks, 'backlinks');
|
||||
tagsNode.innerHTML = renderPageSettingsLocalIndexList(summary.tags, 'tags');
|
||||
var status = summary.status || {};
|
||||
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
|
||||
var includePaths = Array.isArray(settings.includePaths) ? settings.includePaths : ['.'];
|
||||
statusNode.textContent = '索引 ' + Number(status.documentCount || 0) + ' 个页面 / ' + Number(status.resourceCount || 0) + ' 个资源 / ' + Number(status.evidenceBlockCount || 0) + ' 个证据块' + (status.scheduledDue ? ',已到计划时间' : '');
|
||||
if (!(rangesNode instanceof HTMLElement) || !rangesNode.contains(document.activeElement)) {
|
||||
renderLocalIndexRangeRows(popover, includePaths, localIndexStatusKind(status), false, status);
|
||||
}
|
||||
if (scheduleModeNode instanceof HTMLSelectElement && document.activeElement !== scheduleModeNode) {
|
||||
scheduleModeNode.value = String(settings.scheduleMode || 'daily');
|
||||
}
|
||||
if (scheduleTimeNode instanceof HTMLInputElement && document.activeElement !== scheduleTimeNode) {
|
||||
scheduleTimeNode.value = String(settings.scheduleTime || '02:00');
|
||||
}
|
||||
if (scheduleDateNode instanceof HTMLInputElement && document.activeElement !== scheduleDateNode) {
|
||||
scheduleDateNode.value = String(settings.scheduleDate || '');
|
||||
}
|
||||
if (runOnChangeNode instanceof HTMLInputElement) {
|
||||
runOnChangeNode.checked = settings.runOnChange === true;
|
||||
}
|
||||
setLocalIndexScheduleControlsDisabled(false);
|
||||
}
|
||||
|
||||
function localIndexStatusKind(status) {
|
||||
if (!status || typeof status !== 'object') return 'fault';
|
||||
if (status.indexExists !== true) return 'fault';
|
||||
if (status.cacheMatchesSettings === true && status.scheduledDue !== true) return 'indexed';
|
||||
return 'indexing';
|
||||
}
|
||||
|
||||
function localIndexStatusLabel(kind) {
|
||||
if (kind === 'indexed') return '已索引';
|
||||
if (kind === 'indexing') return '索引中';
|
||||
return '索引故障';
|
||||
}
|
||||
|
||||
function localIndexPathStatusKind(path, status, fallbackKind) {
|
||||
if (fallbackKind === 'fault' || fallbackKind === 'indexing') return fallbackKind;
|
||||
var indexedPaths = Array.isArray(status && status.indexedPaths) ? status.indexedPaths : [];
|
||||
var normalizedPath = String(path || '').trim() || '.';
|
||||
if (indexedPaths.indexOf(normalizedPath) >= 0 && status.cacheMatchesSettings === true) return 'indexed';
|
||||
if (status.indexExists === true) return 'indexing';
|
||||
return 'fault';
|
||||
}
|
||||
|
||||
function renderLocalIndexRangeRows(popover, includePaths, fallbackKind, disabled, status) {
|
||||
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
|
||||
if (!(rangesNode instanceof HTMLElement)) return;
|
||||
var rawPaths = Array.isArray(includePaths) ? includePaths.map(function(value) {
|
||||
return String(value || '').trim();
|
||||
}) : [];
|
||||
var paths = disabled ? rawPaths.filter(Boolean) : rawPaths;
|
||||
if (!paths.length && !disabled) paths = ['.'];
|
||||
rangesNode.innerHTML = paths.map(function(path, index) {
|
||||
var kind = localIndexPathStatusKind(path, status || {}, fallbackKind || 'fault');
|
||||
return '' +
|
||||
'<div class="wolai-page-settings-index-range-row" data-local-index-range-row="true">' +
|
||||
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(localIndexStatusLabel(kind)) + '"></span>' +
|
||||
'<input type="text" class="wolai-page-settings-index-path" data-local-index-range-input="true" value="' + escapeHtml(path) + '" spellcheck="false"' + (disabled ? ' disabled' : '') + ' />' +
|
||||
'<button type="button" class="wolai-page-settings-index-remove" data-local-index-action="remove-path" data-local-index-path-index="' + String(index) + '"' + (disabled ? ' disabled' : '') + ' aria-label="删除索引范围">×</button>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function setLocalIndexScheduleControlsDisabled(disabled) {
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
[
|
||||
'[data-testid="wolai-page-settings-local-index-schedule-mode"]',
|
||||
'[data-testid="wolai-page-settings-local-index-schedule-time"]',
|
||||
'[data-testid="wolai-page-settings-local-index-schedule-date"]',
|
||||
'[data-testid="wolai-page-settings-local-index-run-on-change"]'
|
||||
].forEach(function(selector) {
|
||||
var node = popover.querySelector(selector);
|
||||
if (node instanceof HTMLInputElement || node instanceof HTMLSelectElement) node.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPageSettingsLocalIndex(force) {
|
||||
@@ -713,50 +828,148 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
pageUiState.localIndexSummary = {
|
||||
scopeKey: scopeKey,
|
||||
loading: true,
|
||||
error: '',
|
||||
backlinks: null,
|
||||
tags: null
|
||||
error: ''
|
||||
};
|
||||
renderPageSettingsLocalIndex();
|
||||
try {
|
||||
var baseParams = new URLSearchParams();
|
||||
baseParams.set('workspaceId', resolveWorkspaceId(document.body));
|
||||
baseParams.set('rootUri', currentRootUri());
|
||||
var backlinksParams = new URLSearchParams(baseParams);
|
||||
backlinksParams.set('documentId', currentDocumentId());
|
||||
var backlinksUrl = '/api/search/local-index/backlinks?' + backlinksParams.toString();
|
||||
var tagsUrl = '/api/search/local-index/tags?' + baseParams.toString();
|
||||
var responses = await Promise.all([
|
||||
fetch(backlinksUrl, { headers: { accept: 'application/json' } }),
|
||||
fetch(tagsUrl, { headers: { accept: 'application/json' } })
|
||||
]);
|
||||
var backlinksPayload = await responses[0].json().catch(function(){ return null; });
|
||||
var tagsPayload = await responses[1].json().catch(function(){ return null; });
|
||||
if (!responses[0].ok || !backlinksPayload || backlinksPayload.ok !== true) {
|
||||
throw new Error('backlinks_' + responses[0].status);
|
||||
}
|
||||
if (!responses[1].ok || !tagsPayload || tagsPayload.ok !== true) {
|
||||
throw new Error('tags_' + responses[1].status);
|
||||
var statusUrl = '/api/search/local-index/status?' + baseParams.toString();
|
||||
var response = await fetch(statusUrl, { headers: { accept: 'application/json' } });
|
||||
var statusPayload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok || !statusPayload || statusPayload.ok !== true) {
|
||||
throw new Error('status_' + response.status);
|
||||
}
|
||||
pageUiState.localIndexSummary = {
|
||||
scopeKey: scopeKey,
|
||||
loading: false,
|
||||
error: '',
|
||||
backlinks: backlinksPayload.result && Array.isArray(backlinksPayload.result.backlinks) ? backlinksPayload.result.backlinks : [],
|
||||
tags: tagsPayload.result && Array.isArray(tagsPayload.result.tags) ? tagsPayload.result.tags : []
|
||||
status: statusPayload.result || {}
|
||||
};
|
||||
} catch (error) {
|
||||
pageUiState.localIndexSummary = {
|
||||
scopeKey: scopeKey,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
backlinks: [],
|
||||
tags: []
|
||||
status: {}
|
||||
};
|
||||
}
|
||||
renderPageSettingsLocalIndex();
|
||||
}
|
||||
|
||||
function localIndexIncludePathsFromForm() {
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
var values = currentLocalIndexRangeValues(popover).map(function(value) {
|
||||
return value.trim();
|
||||
}).filter(Boolean);
|
||||
return values.length ? values : ['.'];
|
||||
}
|
||||
|
||||
function currentLocalIndexRangeValues(popover) {
|
||||
var root = popover || ensureLocalIndexSettingsPopover();
|
||||
return Array.from(root.querySelectorAll('[data-local-index-range-input]')).map(function(input) {
|
||||
return input instanceof HTMLInputElement ? input.value : '';
|
||||
});
|
||||
}
|
||||
|
||||
function localIndexSchedulePayloadFromForm() {
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
var modeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
|
||||
var timeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
|
||||
var dateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
|
||||
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
|
||||
return {
|
||||
scheduleMode: modeNode instanceof HTMLSelectElement ? modeNode.value : 'daily',
|
||||
scheduleTime: timeNode instanceof HTMLInputElement ? (timeNode.value || '02:00') : '02:00',
|
||||
scheduleDate: dateNode instanceof HTMLInputElement ? dateNode.value : '',
|
||||
runOnChange: runOnChangeNode instanceof HTMLInputElement ? runOnChangeNode.checked : false
|
||||
};
|
||||
}
|
||||
|
||||
function addLocalIndexRange() {
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
var summary = pageUiState.localIndexSummary || {};
|
||||
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover).concat(['']), localIndexStatusKind(summary.status || {}), false, summary.status || {});
|
||||
var inputs = Array.from(popover.querySelectorAll('[data-local-index-range-input]'));
|
||||
var last = inputs[inputs.length - 1];
|
||||
if (last instanceof HTMLInputElement) last.focus();
|
||||
}
|
||||
|
||||
function removeLocalIndexRange(index) {
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
var summary = pageUiState.localIndexSummary || {};
|
||||
var values = currentLocalIndexRangeValues(popover);
|
||||
values.splice(Number(index || 0), 1);
|
||||
if (!values.length) values = [''];
|
||||
renderLocalIndexRangeRows(popover, values, localIndexStatusKind(summary.status || {}), false, summary.status || {});
|
||||
}
|
||||
|
||||
async function persistLocalIndexSettings() {
|
||||
if (!pageSettingsLocalIndexIsAvailable()) return;
|
||||
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
||||
scopeKey: pageSettingsLocalIndexScopeKey(),
|
||||
loading: true,
|
||||
error: ''
|
||||
});
|
||||
renderPageSettingsLocalIndex();
|
||||
try {
|
||||
var response = await fetch('/api/search/local-index/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify(Object.assign({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
rootUri: currentRootUri(),
|
||||
includePaths: localIndexIncludePathsFromForm()
|
||||
}, localIndexSchedulePayloadFromForm()))
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_settings_' + response.status);
|
||||
}
|
||||
await loadPageSettingsLocalIndex(true);
|
||||
} catch (error) {
|
||||
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
||||
scopeKey: pageSettingsLocalIndexScopeKey(),
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
renderPageSettingsLocalIndex();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLocalIndex() {
|
||||
if (!pageSettingsLocalIndexIsAvailable()) return;
|
||||
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
||||
scopeKey: pageSettingsLocalIndexScopeKey(),
|
||||
loading: true,
|
||||
error: ''
|
||||
});
|
||||
renderPageSettingsLocalIndex();
|
||||
try {
|
||||
var response = await fetch('/api/search/local-index/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
rootUri: currentRootUri()
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_refresh_' + response.status);
|
||||
}
|
||||
await loadPageSettingsLocalIndex(true);
|
||||
} catch (error) {
|
||||
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
||||
scopeKey: pageSettingsLocalIndexScopeKey(),
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
renderPageSettingsLocalIndex();
|
||||
}
|
||||
}
|
||||
|
||||
function renderPageSettingsPopover() {
|
||||
var popover = ensurePageSettingsPopover();
|
||||
var options = currentPageOptions();
|
||||
@@ -780,7 +993,6 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
'<span>块数 ' + Number(stats.blockCount || 0) + '</span>' +
|
||||
'<span>待办 ' + Number(stats.todoDone || 0) + '/' + Number(stats.todoTotal || 0) + '</span>';
|
||||
}
|
||||
renderPageSettingsLocalIndex(popover);
|
||||
}
|
||||
|
||||
function setActivePageSettingsTab(tabName) {
|
||||
@@ -793,7 +1005,6 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
popover.querySelectorAll('[data-page-settings-panel]').forEach(function(panel) {
|
||||
panel.hidden = panel.getAttribute('data-page-settings-panel') !== tabName;
|
||||
});
|
||||
if (tabName === 'index') void loadPageSettingsLocalIndex(false);
|
||||
}
|
||||
|
||||
async function persistPageOptionsPatch(patch) {
|
||||
@@ -848,9 +1059,8 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
}
|
||||
|
||||
async function loadPageWidthPreferences() {
|
||||
if (!currentDocumentId()) return;
|
||||
var params = new URLSearchParams();
|
||||
params.set('documentId', currentDocumentId());
|
||||
if (currentDocumentId()) params.set('documentId', currentDocumentId());
|
||||
params.set('workspaceId', resolveWorkspaceId(document.body));
|
||||
var sourcePayload = currentWorkspaceSourcePayload();
|
||||
Object.keys(sourcePayload || {}).forEach(function(key) {
|
||||
@@ -868,6 +1078,7 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
document.documentElement.setAttribute('data-mnote-local-ocr-auto-enabled', String(pageUiState.localOcrPreferences['localOcr.autoEnabled'] === true));
|
||||
applyPageOptionsToShell();
|
||||
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
||||
if (isLocalOcrSettingsOpen()) renderLocalOcrOptions(ensureLocalOcrSettingsPopover());
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -876,7 +1087,8 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
var next = Object.assign({}, previous, { 'localOcr.autoEnabled': Boolean(enabled) });
|
||||
pageUiState.localOcrPreferences = next;
|
||||
window.__MNOTE_LOCAL_OCR_PREFERENCES = Object.assign({}, next);
|
||||
renderPageSettingsPopover();
|
||||
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
||||
if (isLocalOcrSettingsOpen()) renderLocalOcrOptions(ensureLocalOcrSettingsPopover());
|
||||
try {
|
||||
var response = await fetch('/api/ui/preferences', {
|
||||
method: 'PUT',
|
||||
@@ -895,11 +1107,13 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
pageUiState.localOcrPreferences = Object.assign({ 'localOcr.autoEnabled': false }, payload.result && payload.result.localOcrPreferences || {});
|
||||
window.__MNOTE_LOCAL_OCR_PREFERENCES = Object.assign({}, pageUiState.localOcrPreferences);
|
||||
document.documentElement.setAttribute('data-mnote-local-ocr-auto-enabled', String(pageUiState.localOcrPreferences['localOcr.autoEnabled'] === true));
|
||||
renderPageSettingsPopover();
|
||||
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
||||
if (isLocalOcrSettingsOpen()) renderLocalOcrOptions(ensureLocalOcrSettingsPopover());
|
||||
} catch (error) {
|
||||
pageUiState.localOcrPreferences = previous;
|
||||
window.__MNOTE_LOCAL_OCR_PREFERENCES = Object.assign({}, previous);
|
||||
renderPageSettingsPopover();
|
||||
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
||||
if (isLocalOcrSettingsOpen()) renderLocalOcrOptions(ensureLocalOcrSettingsPopover());
|
||||
document.documentElement.setAttribute('data-mnote-local-ocr-preference-error', error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
@@ -953,16 +1167,51 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
return popover instanceof HTMLElement && !popover.hidden;
|
||||
}
|
||||
|
||||
function openPageSettingsPopover() {
|
||||
function isLocalIndexSettingsOpen() {
|
||||
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
||||
return popover instanceof HTMLElement && !popover.hidden;
|
||||
}
|
||||
|
||||
function isLocalOcrSettingsOpen() {
|
||||
var popover = document.querySelector('[data-testid="mnote-local-ocr-settings-popover"]');
|
||||
return popover instanceof HTMLElement && !popover.hidden;
|
||||
}
|
||||
|
||||
function isAnySettingsOpen() {
|
||||
return isPageSettingsOpen() || isLocalIndexSettingsOpen() || isLocalOcrSettingsOpen();
|
||||
}
|
||||
|
||||
function openPageSettingsPopover(initialTab) {
|
||||
if (!currentDocumentId()) return;
|
||||
closeLocalIndexSettingsPopover();
|
||||
closeLocalOcrSettingsPopover();
|
||||
var popover = ensurePageSettingsPopover();
|
||||
renderPageSettingsPopover();
|
||||
setActivePageSettingsTab('page');
|
||||
setActivePageSettingsTab(initialTab || 'page');
|
||||
popover.hidden = false;
|
||||
pageUiState.pageSettingsOpen = true;
|
||||
updatePageSettingsTriggerState();
|
||||
}
|
||||
|
||||
function openPageIndexSettingsPopover() {
|
||||
closePageSettingsPopover();
|
||||
closeLocalOcrSettingsPopover();
|
||||
var popover = ensureLocalIndexSettingsPopover();
|
||||
renderPageSettingsLocalIndex(popover);
|
||||
popover.hidden = false;
|
||||
void loadPageSettingsLocalIndex(false);
|
||||
updateStandaloneSettingsTriggerState();
|
||||
}
|
||||
|
||||
function openLocalOcrSettingsPopover() {
|
||||
closePageSettingsPopover();
|
||||
closeLocalIndexSettingsPopover();
|
||||
var popover = ensureLocalOcrSettingsPopover();
|
||||
renderLocalOcrOptions(popover);
|
||||
popover.hidden = false;
|
||||
updateStandaloneSettingsTriggerState();
|
||||
}
|
||||
|
||||
function closePageSettingsPopover() {
|
||||
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
||||
if (popover instanceof HTMLElement) popover.hidden = true;
|
||||
@@ -970,6 +1219,24 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
updatePageSettingsTriggerState();
|
||||
}
|
||||
|
||||
function closeLocalIndexSettingsPopover() {
|
||||
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
||||
if (popover instanceof HTMLElement) popover.hidden = true;
|
||||
updateStandaloneSettingsTriggerState();
|
||||
}
|
||||
|
||||
function closeLocalOcrSettingsPopover() {
|
||||
var popover = document.querySelector('[data-testid="mnote-local-ocr-settings-popover"]');
|
||||
if (popover instanceof HTMLElement) popover.hidden = true;
|
||||
updateStandaloneSettingsTriggerState();
|
||||
}
|
||||
|
||||
function closeAllSettingsPopovers() {
|
||||
closePageSettingsPopover();
|
||||
closeLocalIndexSettingsPopover();
|
||||
closeLocalOcrSettingsPopover();
|
||||
}
|
||||
|
||||
function togglePageSettingsPopover() {
|
||||
if (isPageSettingsOpen()) closePageSettingsPopover();
|
||||
else openPageSettingsPopover();
|
||||
@@ -979,28 +1246,46 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
applyPageOptionsToShell();
|
||||
});
|
||||
|
||||
window.addEventListener('mnote:open-local-ocr-settings', function() {
|
||||
openLocalOcrSettingsPopover();
|
||||
});
|
||||
|
||||
return {
|
||||
addLocalIndexRange,
|
||||
applyPageOptionsToShell,
|
||||
closeAllSettingsPopovers,
|
||||
closePageHistoryDrawer,
|
||||
closeLocalIndexSettingsPopover,
|
||||
closeLocalOcrSettingsPopover,
|
||||
closePageSettingsPopover,
|
||||
closePageShareDialog,
|
||||
currentLocalOcrPreferences,
|
||||
currentPageOptions,
|
||||
ensureHistorySnapshotsSeeded,
|
||||
ensureLocalIndexSettingsPopover,
|
||||
ensureLocalOcrSettingsPopover,
|
||||
ensurePageHistoryDrawer,
|
||||
ensurePageSettingsPopover,
|
||||
ensurePageShareDialog,
|
||||
isAnySettingsOpen,
|
||||
isLocalIndexSettingsOpen,
|
||||
isLocalOcrSettingsOpen,
|
||||
isPageSettingsOpen,
|
||||
openPageHistoryDrawer,
|
||||
openLocalOcrSettingsPopover,
|
||||
openPageSettingsPopover,
|
||||
openPageIndexSettingsPopover,
|
||||
openPageShareDialog,
|
||||
pageOptionIsSupported,
|
||||
persistLocalOcrAutoPreference,
|
||||
persistLocalIndexSettings,
|
||||
persistPageOptionsPatch,
|
||||
persistPageWidthPreference,
|
||||
recordPageHistorySnapshot,
|
||||
removeLocalIndexRange,
|
||||
renderPageSettingsPopover,
|
||||
setActivePageSettingsTab,
|
||||
refreshLocalIndex,
|
||||
togglePageSettingsPopover,
|
||||
updatePageSettingsTriggerState,
|
||||
writeGlobalShowHeadingNumbers,
|
||||
|
||||
@@ -43,7 +43,11 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
var fileTreeLazyCacheRootUri = '';
|
||||
var fileTreeExpansionStorageRootUri = '';
|
||||
var fileTreeExpansionRestoreTimer = 0;
|
||||
var fileTreeVisibleHydrateTimer = 0;
|
||||
var fileTreeVisibleHydrateQueued = false;
|
||||
var fileTreeCommandBatchRefreshParents = new Map();
|
||||
var FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX = 20;
|
||||
var FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH = 2;
|
||||
var FILETREE_EXPANSION_STORAGE_KEY = 'mnote.localFileTree.expandedRelativePaths.v1';
|
||||
var SIDEBAR_TREE_VIEW_STATE_KEY = 'mnote.sidebarTreeViewState.v1:{userId}:{workspaceId}:{sourceKind}:{treeKind}:{rootUriHash}:{scopeHash}';
|
||||
var SIDEBAR_TREE_VIEW_STATE_API_KEY = 'sidebarTreeViewState.v1';
|
||||
@@ -337,6 +341,24 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
parents.add(normalized);
|
||||
}
|
||||
|
||||
function watchBatchPathIsMarkdown(item) {
|
||||
var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim().toLowerCase();
|
||||
return relativePath.endsWith('.md') || relativePath.endsWith('.markdown');
|
||||
}
|
||||
|
||||
function watchBatchEventKind(item) {
|
||||
return String(item && (item.kind || item.eventKind || item.event_kind) || '').trim();
|
||||
}
|
||||
|
||||
function watchBatchNeedsPageTreeRefresh(item) {
|
||||
if (!watchBatchPathIsMarkdown(item)) return false;
|
||||
var kind = watchBatchEventKind(item);
|
||||
return kind.indexOf('Create') >= 0
|
||||
|| kind.indexOf('Remove') >= 0
|
||||
|| kind.indexOf('Modify(Name') >= 0
|
||||
|| kind.indexOf('Rename') >= 0;
|
||||
}
|
||||
|
||||
function addAffectedParentsFromCommandResult(parents, result) {
|
||||
var affectedParents = Array.isArray(result && result.affectedParents)
|
||||
? result.affectedParents
|
||||
@@ -452,10 +474,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
affectedParents.forEach(function(parent) {
|
||||
addCommandRefreshParent(parents, parent && (parent.relativePath || parent.relative_path));
|
||||
});
|
||||
var needsSidebarRefresh = changedPaths.some(function(item) {
|
||||
var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim().toLowerCase();
|
||||
return relativePath.endsWith('.md') || relativePath.endsWith('.markdown');
|
||||
});
|
||||
var needsSidebarRefresh = changedPaths.some(watchBatchNeedsPageTreeRefresh);
|
||||
document.documentElement.setAttribute('data-mnote-local-folder-watch-batch-applied', String(batch.revision || 'true'));
|
||||
void Promise.all(Array.from(parents).map(function(parentRelativePath) {
|
||||
return refreshFileTreeParent(parentRelativePath).catch(function(error) {
|
||||
@@ -465,6 +484,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
})).then(function() {
|
||||
if (needsSidebarRefresh) {
|
||||
void refreshLocalFolderSidebarSnapshot();
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-mnote-local-folder-watch-sidebar-refresh-skipped', 'content-only');
|
||||
}
|
||||
markLocalFolderWatchApplied('watch_batch');
|
||||
});
|
||||
@@ -587,6 +608,11 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
return Boolean(resolved && Array.isArray(resolved.items));
|
||||
}
|
||||
|
||||
function hasProjectionItemArray(projection) {
|
||||
var resolved = readProjection(projection);
|
||||
return Boolean(resolved && Array.isArray(resolved.items));
|
||||
}
|
||||
|
||||
function nodeIdOf(item) {
|
||||
var runtimeFn = fileTreeRuntimeFunction('nodeIdOf');
|
||||
if (runtimeFn) return runtimeFn(item);
|
||||
@@ -1238,6 +1264,81 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function scheduleFileTreeIdleTask(callback, timeout) {
|
||||
if (typeof window.requestIdleCallback === 'function') {
|
||||
return window.requestIdleCallback(callback, { timeout: timeout || 800 });
|
||||
}
|
||||
if (typeof window.requestAnimationFrame === 'function') {
|
||||
return window.requestAnimationFrame(function() {
|
||||
window.setTimeout(callback, 0);
|
||||
});
|
||||
}
|
||||
return window.setTimeout(callback, 0);
|
||||
}
|
||||
|
||||
function isFileTreeRowVisibleForHydrate(row) {
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
if (typeof row.getBoundingClientRect !== 'function') return true;
|
||||
var rect = row.getBoundingClientRect();
|
||||
var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
|
||||
if (!viewportHeight) return true;
|
||||
return rect.bottom >= -64 && rect.top <= viewportHeight + 256;
|
||||
}
|
||||
|
||||
function fileTreeRowNeedsVisibleHydrate(row) {
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
if (currentSourceKind() !== 'local_folder') return false;
|
||||
if (!currentRootUri()) return false;
|
||||
if (row.getAttribute('data-shell-mode') !== 'filetree') return false;
|
||||
if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return false;
|
||||
if (row.getAttribute('aria-expanded') === 'true') {
|
||||
var relativePath = localFileTreeRelativePathFromRow(row);
|
||||
if (!relativePath || !fileTreeExpandedRelativePaths.has(relativePath)) return false;
|
||||
if (row.getAttribute('data-filetree-children-loaded') !== 'true' && row.getAttribute('data-filetree-children-loading') !== 'true') {
|
||||
return isFileTreeRowVisibleForHydrate(row);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function collectVisibleExpandedFileTreeRowsForHydrate() {
|
||||
var rows = [];
|
||||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
|
||||
if (rows.length >= FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX) return;
|
||||
if (fileTreeRowNeedsVisibleHydrate(row)) rows.push(row);
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
function scheduleHydrateVisibleExpandedFileTreeRows(reason) {
|
||||
if (fileTreeVisibleHydrateQueued) return;
|
||||
if (currentSourceKind() !== 'local_folder') return;
|
||||
fileTreeVisibleHydrateQueued = true;
|
||||
fileTreeVisibleHydrateTimer = scheduleFileTreeIdleTask(function() {
|
||||
fileTreeVisibleHydrateTimer = 0;
|
||||
fileTreeVisibleHydrateQueued = false;
|
||||
hydrateVisibleExpandedFileTreeRows(reason || 'idle');
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function hydrateVisibleExpandedFileTreeRows(reason) {
|
||||
if (currentSourceKind() !== 'local_folder') return false;
|
||||
var rows = collectVisibleExpandedFileTreeRowsForHydrate();
|
||||
if (!rows.length) return false;
|
||||
var batch = rows.slice(0, FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH);
|
||||
batch.forEach(function(row) {
|
||||
var button = row.querySelector('[data-rust-action="toggle"]');
|
||||
void loadFileTreeChildren(row, button, { persist: false, idleHydrate: true }).then(function(loaded) {
|
||||
if (loaded) {
|
||||
document.documentElement.setAttribute('data-mnote-filetree-idle-hydrate-applied', String(reason || 'idle'));
|
||||
scheduleHydrateVisibleExpandedFileTreeRows('cascade');
|
||||
}
|
||||
});
|
||||
});
|
||||
if (rows.length > batch.length) scheduleHydrateVisibleExpandedFileTreeRows('batch');
|
||||
return true;
|
||||
}
|
||||
|
||||
function rememberFileTreeExpansionState() {
|
||||
var changed = false;
|
||||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
|
||||
@@ -1300,6 +1401,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
tree.replaceChildren(template.content.cloneNode(true));
|
||||
reprojectFileTreeSelectionState();
|
||||
scheduleRestorePersistedFileTreeExpansionState();
|
||||
scheduleHydrateVisibleExpandedFileTreeRows('render');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1329,28 +1431,29 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
setTreeRowExpanded(row, button, wasExpanded);
|
||||
syncSidebarFileTreeSelection();
|
||||
reprojectFileTreeSelectionState();
|
||||
scheduleHydrateVisibleExpandedFileTreeRows('patch');
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderSidebarSnapshot(payload) {
|
||||
var renderedPage = false;
|
||||
if (hasProjectionItems(payload)) {
|
||||
if (hasProjectionItemArray(payload)) {
|
||||
renderedPage = true;
|
||||
void renderPageProjection(payload);
|
||||
}
|
||||
var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection');
|
||||
var renderedFile = fileProjection && hasProjectionItems(fileProjection) ? renderFileProjection(fileProjection) : false;
|
||||
var renderedFile = fileProjection && hasProjectionItemArray(fileProjection) ? renderFileProjection(fileProjection) : false;
|
||||
return renderedPage || renderedFile;
|
||||
}
|
||||
|
||||
function renderLiveSidebarSnapshot(payload) {
|
||||
var renderedPage = false;
|
||||
if (hasProjectionItems(payload)) {
|
||||
if (hasProjectionItemArray(payload)) {
|
||||
renderedPage = true;
|
||||
void renderPageProjection(payload);
|
||||
}
|
||||
var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection');
|
||||
var hasFileProjection = fileProjection && hasProjectionItems(fileProjection);
|
||||
var hasFileProjection = fileProjection && hasProjectionItemArray(fileProjection);
|
||||
if (currentFileTreeScope()) {
|
||||
if (!hasFileProjection) return renderedPage;
|
||||
var projectionParent = projectionParentRelativePath(fileProjection);
|
||||
@@ -1473,9 +1576,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
var sidebarPayload = sidebarResponse && sidebarResponse.ok ? await sidebarResponse.json().catch(function() { return null; }) : null;
|
||||
var resolvedSidebarPayload = sidebarPayload ? (sidebarPayload.result || sidebarPayload) : null;
|
||||
var renderedPage = false;
|
||||
if (resolvedSidebarPayload && hasProjectionItems(resolvedSidebarPayload)) {
|
||||
renderedPage = true;
|
||||
void renderPageProjection(resolvedSidebarPayload);
|
||||
if (resolvedSidebarPayload && hasProjectionItemArray(resolvedSidebarPayload)) {
|
||||
renderedPage = await renderPageProjection(resolvedSidebarPayload);
|
||||
}
|
||||
if (renderedFile && fileTreeScope) {
|
||||
var fileRoot = document.getElementById('sidebar-file-tree-root');
|
||||
@@ -1745,6 +1847,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
ensureFileTreeLazyCacheScope();
|
||||
if (!fileTreeExpandedRelativePaths.size) return false;
|
||||
var restored = false;
|
||||
var needsVisibleHydrate = false;
|
||||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return;
|
||||
@@ -1763,12 +1866,12 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
restored = true;
|
||||
return;
|
||||
}
|
||||
void loadFileTreeChildren(row, button, { persist: false }).then(function(loaded) {
|
||||
if (loaded) restorePersistedFileTreeExpansionState();
|
||||
});
|
||||
setTreeRowExpanded(row, button, true, { persist: false });
|
||||
needsVisibleHydrate = true;
|
||||
restored = true;
|
||||
});
|
||||
if (restored) document.documentElement.setAttribute('data-mnote-filetree-expansion-restored', 'true');
|
||||
if (needsVisibleHydrate) scheduleHydrateVisibleExpandedFileTreeRows('restore');
|
||||
return restored;
|
||||
}
|
||||
|
||||
@@ -1784,6 +1887,9 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
flushPendingSidebarTreeViewState('filetree');
|
||||
flushPendingSidebarTreeViewState('pagetree');
|
||||
});
|
||||
document.addEventListener('scroll', function() {
|
||||
scheduleHydrateVisibleExpandedFileTreeRows('scroll');
|
||||
}, true);
|
||||
|
||||
window.addEventListener('tree:title-updated', function(event) {
|
||||
var detail = event.detail || {};
|
||||
@@ -1927,6 +2033,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
|
||||
refreshLocalFolderSidebarSnapshot,
|
||||
refreshLocalFolderAfterCommand,
|
||||
removeDocumentRowForMode,
|
||||
renderPageProjection,
|
||||
renderSidebarSnapshot,
|
||||
revealFileTreeResource,
|
||||
restorePersistedFileTreeExpansionState,
|
||||
|
||||
@@ -200,17 +200,25 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
});
|
||||
const applyPageOptionsToShell = (...args) => sidebarPageSettings.applyPageOptionsToShell(...args);
|
||||
const closePageHistoryDrawer = (...args) => sidebarPageSettings.closePageHistoryDrawer(...args);
|
||||
const closeAllSettingsPopovers = (...args) => sidebarPageSettings.closeAllSettingsPopovers(...args);
|
||||
const closePageSettingsPopover = (...args) => sidebarPageSettings.closePageSettingsPopover(...args);
|
||||
const closePageShareDialog = (...args) => sidebarPageSettings.closePageShareDialog(...args);
|
||||
const currentPageOptions = (...args) => sidebarPageSettings.currentPageOptions(...args);
|
||||
const ensureHistorySnapshotsSeeded = (...args) => sidebarPageSettings.ensureHistorySnapshotsSeeded(...args);
|
||||
const isAnySettingsOpen = (...args) => sidebarPageSettings.isAnySettingsOpen(...args);
|
||||
const isPageSettingsOpen = (...args) => sidebarPageSettings.isPageSettingsOpen(...args);
|
||||
const openPageHistoryDrawer = (...args) => sidebarPageSettings.openPageHistoryDrawer(...args);
|
||||
const openPageIndexSettingsPopover = (...args) => sidebarPageSettings.openPageIndexSettingsPopover(...args);
|
||||
const openLocalOcrSettingsPopover = (...args) => sidebarPageSettings.openLocalOcrSettingsPopover(...args);
|
||||
const openPageShareDialog = (...args) => sidebarPageSettings.openPageShareDialog(...args);
|
||||
const pageOptionIsSupported = (...args) => sidebarPageSettings.pageOptionIsSupported(...args);
|
||||
const addLocalIndexRange = (...args) => sidebarPageSettings.addLocalIndexRange(...args);
|
||||
const persistLocalOcrAutoPreference = (...args) => sidebarPageSettings.persistLocalOcrAutoPreference(...args);
|
||||
const persistLocalIndexSettings = (...args) => sidebarPageSettings.persistLocalIndexSettings(...args);
|
||||
const persistPageOptionsPatch = (...args) => sidebarPageSettings.persistPageOptionsPatch(...args);
|
||||
const persistPageWidthPreference = (...args) => sidebarPageSettings.persistPageWidthPreference(...args);
|
||||
const refreshLocalIndex = (...args) => sidebarPageSettings.refreshLocalIndex(...args);
|
||||
const removeLocalIndexRange = (...args) => sidebarPageSettings.removeLocalIndexRange(...args);
|
||||
const recordPageHistorySnapshot = (...args) => sidebarPageSettings.recordPageHistorySnapshot(...args);
|
||||
const renderPageSettingsPopover = (...args) => sidebarPageSettings.renderPageSettingsPopover(...args);
|
||||
const setActivePageSettingsTab = (...args) => sidebarPageSettings.setActivePageSettingsTab(...args);
|
||||
@@ -351,6 +359,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
const applyRemoveDocumentDelta = (...args) => sidebarTreeLiveApply.applyRemoveDocumentDelta(...args);
|
||||
const setTreeLiveApplyError = (...args) => sidebarTreeLiveApply.setTreeLiveApplyError(...args);
|
||||
const objectIdentityAttr = (...args) => sidebarTreeLiveApply.objectIdentityAttr(...args);
|
||||
const renderPageProjection = (...args) => sidebarTreeLiveApply.renderPageProjection(...args);
|
||||
const renderSidebarSnapshot = (...args) => sidebarTreeLiveApply.renderSidebarSnapshot(...args);
|
||||
const refreshLocalFolderSidebarSnapshot = (...args) => sidebarTreeLiveApply.refreshLocalFolderSidebarSnapshot(...args);
|
||||
const startLocalFolderSidebarWatch = (...args) => sidebarTreeLiveApply.startLocalFolderSidebarWatch(...args);
|
||||
@@ -432,6 +441,21 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return true;
|
||||
}
|
||||
|
||||
function openCurrentNavigationPage(trigger) {
|
||||
if (currentSourceKind() === 'local_folder') {
|
||||
var rootUri = currentRootUri();
|
||||
var scope = currentFileTreeScope();
|
||||
var workspaceId = currentWorkspaceId() || sidebarShortcutWorkspaceId() || resolveWorkspaceId(trigger || document.body);
|
||||
var title = scope ? scope.split('/').filter(Boolean).pop() : '本地文件夹';
|
||||
return openNavigationPageForFolder(rootUri, scope, workspaceId, title || scope || '本地文件夹');
|
||||
}
|
||||
var targetUrl = new URL('/', window.location.origin);
|
||||
var workspaceId = currentWorkspaceId() || resolveWorkspaceId(trigger || document.body);
|
||||
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
|
||||
window.location.assign(targetUrl.pathname + targetUrl.search);
|
||||
return true;
|
||||
}
|
||||
|
||||
function currentTopbarTitle() {
|
||||
var title = document.querySelector('[data-page-title-current="true"]');
|
||||
return title && title.textContent ? title.textContent.trim() : '无标题';
|
||||
@@ -821,9 +845,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
var sidebarPayload = await sidebarResponse.json().catch(function() { return null; });
|
||||
if (!sidebarResponse.ok) throw new Error(sidebarPayload && (sidebarPayload.error || sidebarPayload.message) || 'local_page_tree_failed_' + sidebarResponse.status);
|
||||
var sidebarProjection = sidebarPayload && (sidebarPayload.result || sidebarPayload) || {};
|
||||
var renderedPage = renderSidebarSnapshot(Object.assign({}, sidebarProjection, {
|
||||
dataset: Object.assign({}, sidebarProjection.dataset || {})
|
||||
}));
|
||||
var renderedPage = sidebarProjection ? await renderPageProjection(sidebarProjection) : false;
|
||||
root = document.getElementById('sidebar-file-tree-root');
|
||||
if (root instanceof HTMLElement) {
|
||||
root.setAttribute('data-workspace-id', workspaceId);
|
||||
@@ -2198,6 +2220,83 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
escapeHtml(cleanTitle.slice(index + cleanQuery.length));
|
||||
}
|
||||
|
||||
function searchResultEvidenceLocator(item) {
|
||||
if (item && item.evidence && item.evidence.source && typeof item.evidence.source === 'object') return item.evidence.source;
|
||||
if (item && item.source && item.source.locator && typeof item.source.locator === 'object') return item.source.locator;
|
||||
if (item && Array.isArray(item.evidence) && item.evidence[0] && item.evidence[0].source && typeof item.evidence[0].source === 'object') return item.evidence[0].source;
|
||||
return null;
|
||||
}
|
||||
|
||||
function evidenceLocatorResourceKind(locator, fallback) {
|
||||
return searchText(locator && (locator.resourceKind || locator.resource_kind) || fallback || '').toLowerCase();
|
||||
}
|
||||
|
||||
function evidenceLocatorBbox(locator) {
|
||||
var bbox = locator && locator.bbox;
|
||||
if (Array.isArray(bbox)) return bbox.slice(0, 4).join(',');
|
||||
if (bbox && typeof bbox === 'object') return [bbox.x0, bbox.y0, bbox.x1, bbox.y1].join(',');
|
||||
return '';
|
||||
}
|
||||
|
||||
function evidenceLocatorOpenUrl(locator) {
|
||||
var action = locator && locator.openAction && typeof locator.openAction === 'object' ? locator.openAction : null;
|
||||
return searchText(action && action.url);
|
||||
}
|
||||
|
||||
async function openEvidenceSearchResult(item, event) {
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
if (!locator) {
|
||||
var fallbackId = searchText(item && (item.documentId || item.nodeId || item.id));
|
||||
if (fallbackId) window.location.assign('/documents/' + encodeURIComponent(fallbackId));
|
||||
return;
|
||||
}
|
||||
var resourcePath = searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path);
|
||||
var ownerDocumentId = searchText(locator.ownerDocumentId || locator.owner_document_id || item.documentId || '');
|
||||
var resourceKind = evidenceLocatorResourceKind(locator, item && item.resourceType);
|
||||
var openTarget = event && event.altKey ? 'side' : 'active-tab';
|
||||
if (resourcePath && resourceKind && resourceKind !== 'markdown') {
|
||||
var fileName = resourcePath.split('/').filter(Boolean).pop() || resourcePath;
|
||||
var localFileUrl = buildLocalFileOpenUrl(resourcePath, false);
|
||||
var href = resourceKind === 'pdf' ? buildPdfPreviewOpenUrl(localFileUrl, fileName) : localFileUrl;
|
||||
await openLocalResourceInActiveTab({
|
||||
path: resourcePath,
|
||||
title: fileName,
|
||||
kind: resourceKind,
|
||||
href: href,
|
||||
assetId: 'local-file:' + resourcePath,
|
||||
documentId: ownerDocumentId || currentDocumentId() || '',
|
||||
ownerDocumentId: ownerDocumentId || currentDocumentId() || '',
|
||||
workspaceId: resolveWorkspaceId(document.body) || '',
|
||||
sourceKind: currentSourceKind() || 'local_folder',
|
||||
rootUri: searchText(locator.rootUri || locator.root_uri || currentRootUri()),
|
||||
paneRole: openTarget === 'side' ? 'secondary' : 'primary',
|
||||
evidenceLocator: locator,
|
||||
page: locator.page,
|
||||
bbox: locator.bbox,
|
||||
sourceMapPath: searchText(locator.sourceMapPath || locator.source_map_path),
|
||||
blockId: searchText(locator.blockId || locator.block_id),
|
||||
lineRange: locator.lineRange || locator.line_range || null,
|
||||
charRange: locator.charRange || locator.char_range || null
|
||||
});
|
||||
closeSearchModal();
|
||||
return;
|
||||
}
|
||||
var url = evidenceLocatorOpenUrl(locator) || ('/documents/' + encodeURIComponent(ownerDocumentId || currentDocumentId() || ''));
|
||||
try {
|
||||
var target = new URL(url, window.location.origin);
|
||||
var blockId = searchText(locator.blockId || locator.block_id);
|
||||
var sourceMapPath = searchText(locator.sourceMapPath || locator.source_map_path);
|
||||
if (blockId) target.searchParams.set('blockId', blockId);
|
||||
if (locator.page) target.searchParams.set('page', String(locator.page));
|
||||
var bbox = evidenceLocatorBbox(locator);
|
||||
if (bbox) target.searchParams.set('bbox', bbox);
|
||||
if (sourceMapPath) target.searchParams.set('sourceMapPath', sourceMapPath);
|
||||
window.location.assign(target.pathname + target.search + target.hash);
|
||||
} catch (_) {
|
||||
window.location.assign(url);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSearchRecentState(overlay) {
|
||||
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
||||
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
||||
@@ -2246,7 +2345,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
filters: {
|
||||
titleOnly: searchSwitchValue(overlay, 'title'),
|
||||
exact: searchSwitchValue(overlay, 'exact'),
|
||||
includeOcr: false,
|
||||
includeOcr: true,
|
||||
onlyCurrentPage: searchSwitchValue(overlay, 'page'),
|
||||
timeRange: 'any',
|
||||
timeField: 'updated'
|
||||
@@ -2263,16 +2362,20 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
}
|
||||
results.innerHTML = items.map(function(item) {
|
||||
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
||||
var snippet = searchText(item.snippet || (Array.isArray(item.evidence) && item.evidence[0] && item.evidence[0].snippet) || '');
|
||||
var snippet = searchText(item.snippet || item.evidence && item.evidence.quote || (Array.isArray(item.evidence) && item.evidence[0] && (item.evidence[0].quote || item.evidence[0].snippet)) || '');
|
||||
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
||||
var resourceType = searchText(item.resourceType || item.resourceKind || 'page');
|
||||
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '">' +
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
var locatorAttr = locator ? escapeHtml(JSON.stringify(locator)) : '';
|
||||
var index = items.indexOf(item);
|
||||
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-search-result-index="' + String(index) + '" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '"' + (locatorAttr ? ' data-evidence-locator="' + locatorAttr + '"' : '') + '>' +
|
||||
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
||||
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchTitle(title, query) + '</span>' +
|
||||
'<span class="wolai-search-result-snippet">' + (snippet ? highlightedHtml(snippet) : escapeHtml(path)) + '</span>' +
|
||||
'<span class="wolai-search-result-path"><span>' + escapeHtml(path) + '</span><span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span></span></span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
window.__mnoteSearchResults = items;
|
||||
} catch (error) {
|
||||
if (requestId !== activeSearchRequestId) return;
|
||||
meta.innerHTML = '<span>共 0 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||
@@ -2473,9 +2576,30 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return;
|
||||
}
|
||||
|
||||
var indexSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-index-settings"]');
|
||||
if (indexSettingsTrigger) {
|
||||
e.preventDefault();
|
||||
openPageIndexSettingsPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
var ocrSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-ocr-settings"], [data-mnote-action="toggle-ocr-tasks"]');
|
||||
if (ocrSettingsTrigger) {
|
||||
e.preventDefault();
|
||||
openLocalOcrSettingsPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsClose = closestAction(e.target, '[data-settings-action="close"]');
|
||||
if (settingsClose) {
|
||||
e.preventDefault();
|
||||
closeAllSettingsPopovers();
|
||||
return;
|
||||
}
|
||||
|
||||
var pageSettingsPanel = closestAction(e.target, '.wolai-page-settings-panel');
|
||||
if (isPageSettingsOpen() && !pageSettingsPanel) {
|
||||
closePageSettingsPopover();
|
||||
if (isAnySettingsOpen() && !pageSettingsPanel) {
|
||||
closeAllSettingsPopovers();
|
||||
}
|
||||
|
||||
var pageSettingsTab = closestAction(e.target, '[data-page-settings-tab]');
|
||||
@@ -2485,6 +2609,39 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return;
|
||||
}
|
||||
|
||||
var localIndexAction = closestAction(e.target, '[data-local-index-action]');
|
||||
if (localIndexAction) {
|
||||
e.preventDefault();
|
||||
var localIndexActionName = localIndexAction.getAttribute('data-local-index-action') || '';
|
||||
if (localIndexActionName === 'save') {
|
||||
void persistLocalIndexSettings();
|
||||
return;
|
||||
}
|
||||
if (localIndexActionName === 'refresh') {
|
||||
void refreshLocalIndex();
|
||||
return;
|
||||
}
|
||||
if (localIndexActionName === 'add-path') {
|
||||
addLocalIndexRange();
|
||||
return;
|
||||
}
|
||||
if (localIndexActionName === 'remove-path') {
|
||||
removeLocalIndexRange(localIndexAction.getAttribute('data-local-index-path-index') || '0');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var localOcrSettingsAction = closestAction(e.target, '[data-local-ocr-settings-action]');
|
||||
if (localOcrSettingsAction) {
|
||||
e.preventDefault();
|
||||
var localOcrSettingsActionName = localOcrSettingsAction.getAttribute('data-local-ocr-settings-action') || '';
|
||||
closeAllSettingsPopovers();
|
||||
window.dispatchEvent(new CustomEvent('mnote:local-ocr-settings-action', {
|
||||
detail: { action: localOcrSettingsActionName }
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
var pageSettingsAction = closestAction(e.target, '[data-page-settings-action]');
|
||||
if (pageSettingsAction) {
|
||||
e.preventDefault();
|
||||
@@ -2507,6 +2664,13 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return;
|
||||
}
|
||||
|
||||
var navigationPageTrigger = closestAction(e.target, '[data-mnote-action="open-navigation-page"]');
|
||||
if (navigationPageTrigger) {
|
||||
e.preventDefault();
|
||||
openCurrentNavigationPage(navigationPageTrigger);
|
||||
return;
|
||||
}
|
||||
|
||||
var searchTrigger = closestAction(e.target, '[data-mnote-action="open-search-modal"]');
|
||||
if (searchTrigger) {
|
||||
e.preventDefault();
|
||||
@@ -2518,6 +2682,24 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
closeSearchModal();
|
||||
return;
|
||||
}
|
||||
var searchResultRow = closestAction(e.target, '[data-testid="wolai-search-result-row"]');
|
||||
if (searchResultRow) {
|
||||
e.preventDefault();
|
||||
var index = Number(searchResultRow.getAttribute('data-search-result-index') || -1);
|
||||
var item = Array.isArray(window.__mnoteSearchResults) && index >= 0 ? window.__mnoteSearchResults[index] : null;
|
||||
if (!item) {
|
||||
var locatorPayload = searchResultRow.getAttribute('data-evidence-locator') || '';
|
||||
try {
|
||||
var locator = locatorPayload ? JSON.parse(locatorPayload) : null;
|
||||
item = locator ? { evidence: { source: locator }, documentId: searchResultRow.getAttribute('data-document-id') || '' } : null;
|
||||
} catch (_) {
|
||||
item = null;
|
||||
}
|
||||
}
|
||||
void openEvidenceSearchResult(item || { documentId: searchResultRow.getAttribute('data-document-id') || '' }, e)
|
||||
.catch(function(error) { console.warn('mnote evidence 搜索结果打开失败', error); });
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceMenuTrigger = closestAction(e.target, '[data-mnote-action="open-workspace-source-menu"]');
|
||||
if (sourceMenuTrigger) {
|
||||
@@ -2847,9 +3029,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.key === 'Escape' && isPageSettingsOpen()) {
|
||||
if (event.key === 'Escape' && isAnySettingsOpen()) {
|
||||
event.preventDefault();
|
||||
closePageSettingsPopover();
|
||||
closeAllSettingsPopovers();
|
||||
return;
|
||||
}
|
||||
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key && event.key.toLowerCase() === 'p') {
|
||||
|
||||
Reference in New Issue
Block a user