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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user