Improve local evidence search and AI capabilities
This commit is contained in:
@@ -986,6 +986,14 @@ pub struct OfficePreviewQuery {
|
||||
source_kind: Option<String>,
|
||||
root_uri: Option<String>,
|
||||
document_id: Option<String>,
|
||||
#[serde(default)]
|
||||
page: Option<u32>,
|
||||
#[serde(default)]
|
||||
bbox: Option<String>,
|
||||
#[serde(default, alias = "sourceMapPath")]
|
||||
source_map_path: Option<String>,
|
||||
#[serde(default, alias = "blockId")]
|
||||
block_id: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Response {
|
||||
@@ -1013,6 +1021,13 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
let source_kind = query.source_kind.unwrap_or_default();
|
||||
let root_uri = query.root_uri.unwrap_or_default();
|
||||
let document_id = query.document_id.unwrap_or_default();
|
||||
let target_page = query
|
||||
.page
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_default();
|
||||
let target_bbox = query.bbox.unwrap_or_default();
|
||||
let target_source_map_path = query.source_map_path.unwrap_or_default();
|
||||
let target_block_id = query.block_id.unwrap_or_default();
|
||||
let html = format!(
|
||||
r#"<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@@ -1047,6 +1062,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
.mnote-office-viewer .mnote-docx-wrapper {{ width: 100%; background: transparent !important; padding: 0 !important; align-items: stretch !important; }}
|
||||
.mnote-office-viewer .docx-wrapper > section.docx,
|
||||
.mnote-office-viewer .mnote-docx-wrapper > section.mnote-docx {{ width: 100% !important; max-width: none !important; margin: 0 0 14px !important; box-shadow: 0 2px 10px rgba(25, 25, 22, .08); }}
|
||||
.mnote-office-viewer {{ position: relative; }}
|
||||
.mnote-office-viewer [data-mnote-office-evidence-target="true"] {{ outline: 0; border-radius: 2px; background: #FFE9E6; color: #D83A32; box-shadow: 0 0 0 1px rgba(216, 58, 50, .22); }}
|
||||
.mnote-office-evidence-marker {{ position: absolute; z-index: 3; left: 24px; max-width: min(720px, calc(100% - 48px)); padding: 6px 10px; border: 1px solid rgba(216, 58, 50, .45); border-radius: 6px; background: rgba(255, 249, 248, .96); color: #D83A32; font-size: 13px; line-height: 1.5; box-shadow: 0 2px 10px rgba(15, 23, 42, .12); }}
|
||||
.mnote-office-evidence-marker[data-mnote-office-evidence-marker-mode="range"] {{ pointer-events: none; background: rgba(255, 233, 230, .72); box-shadow: 0 0 0 1px rgba(216, 58, 50, .28); }}
|
||||
.mnote-office-pptx-stage {{ width: 100%; overflow: auto; }}
|
||||
.mnote-office-pptx-stage .pptx-preview-wrapper {{ max-width: 100%; background: transparent !important; }}
|
||||
.mnote-office-pptx-stage .pptx-preview-slide-wrapper {{ box-shadow: 0 2px 10px rgba(25, 25, 22, .08); }}
|
||||
@@ -1055,7 +1074,7 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body data-file-url="{file_url}" data-file-name="{file_name}" data-file-type="{file_type}" data-page-width-content-type="{page_width_content_type}" data-workspace-id="{workspace_id}" data-mnote-source-kind="{source_kind}" data-mnote-root-uri="{root_uri}" data-document-id="{document_id}">
|
||||
<body data-file-url="{file_url}" data-file-name="{file_name}" data-file-type="{file_type}" data-page-width-content-type="{page_width_content_type}" data-workspace-id="{workspace_id}" data-mnote-source-kind="{source_kind}" data-mnote-root-uri="{root_uri}" data-document-id="{document_id}" data-evidence-page="{target_page}" data-evidence-bbox="{target_bbox}" data-evidence-source-map-path="{target_source_map_path}" data-evidence-block-id="{target_block_id}">
|
||||
<main class="mnote-office-preview">
|
||||
<section class="mnote-office-viewer" id="mnote-office-viewer"></section>
|
||||
</main>
|
||||
@@ -1070,6 +1089,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
const fileName = body.dataset.fileName || '';
|
||||
const fileType = (body.dataset.fileType || '').toLowerCase();
|
||||
const pageWidthContentType = body.dataset.pageWidthContentType || 'word';
|
||||
let evidencePage = Number(body.dataset.evidencePage || 0);
|
||||
let evidenceBbox = body.dataset.evidenceBbox || '';
|
||||
let evidenceSourceMapPath = body.dataset.evidenceSourceMapPath || '';
|
||||
let evidenceBlockId = body.dataset.evidenceBlockId || '';
|
||||
let currentPptxBuffer = null;
|
||||
let pptxRenderToken = 0;
|
||||
let pptxResizeTimer = 0;
|
||||
@@ -1136,6 +1159,299 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
viewer.append(message);
|
||||
}}
|
||||
|
||||
function normalizeEvidenceText(value) {{
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}}
|
||||
|
||||
function markEvidenceTarget(target) {{
|
||||
if (!(target instanceof HTMLElement)) return false;
|
||||
viewer.querySelectorAll('[data-mnote-office-evidence-target="true"]').forEach(node => {{
|
||||
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-office-evidence-target');
|
||||
}});
|
||||
target.setAttribute('data-mnote-office-evidence-target', 'true');
|
||||
window.setTimeout(() => target.scrollIntoView({{ block: 'center', inline: 'nearest' }}), 0);
|
||||
document.documentElement.setAttribute('data-mnote-office-evidence-applied', 'true');
|
||||
return true;
|
||||
}}
|
||||
|
||||
function normalizedTextWithRawOffsets(value) {{
|
||||
const raw = String(value || '');
|
||||
let text = '';
|
||||
const offsets = [];
|
||||
let previousWhitespace = true;
|
||||
for (let index = 0; index < raw.length; index += 1) {{
|
||||
const ch = raw[index];
|
||||
if (/\s/.test(ch)) {{
|
||||
if (text && !previousWhitespace) {{
|
||||
text += ' ';
|
||||
offsets.push(index);
|
||||
}}
|
||||
previousWhitespace = true;
|
||||
}} else {{
|
||||
text += ch;
|
||||
offsets.push(index);
|
||||
previousWhitespace = false;
|
||||
}}
|
||||
}}
|
||||
if (text.endsWith(' ')) {{
|
||||
text = text.slice(0, -1);
|
||||
offsets.pop();
|
||||
}}
|
||||
return {{ text, offsets }};
|
||||
}}
|
||||
|
||||
function compactTextWithRawOffsets(value) {{
|
||||
const raw = String(value || '');
|
||||
let text = '';
|
||||
const offsets = [];
|
||||
for (let index = 0; index < raw.length; index += 1) {{
|
||||
const ch = raw[index];
|
||||
if (/\s/.test(ch)) continue;
|
||||
text += ch;
|
||||
offsets.push(index);
|
||||
}}
|
||||
return {{ text, offsets }};
|
||||
}}
|
||||
|
||||
function wrapEvidenceTextNode(node, needle) {{
|
||||
if (!(node instanceof Text)) return null;
|
||||
const raw = String(node.textContent || '');
|
||||
let start = raw.indexOf(needle);
|
||||
let end = start >= 0 ? start + needle.length : -1;
|
||||
if (start < 0) {{
|
||||
const compact = compactTextWithRawOffsets(raw);
|
||||
const compactNeedle = normalizeEvidenceText(needle).replace(/\s+/g, '');
|
||||
let normalizedStart = compact.text.indexOf(compactNeedle);
|
||||
let sourceOffsets = compact.offsets;
|
||||
if (normalizedStart < 0) {{
|
||||
const mapped = normalizedTextWithRawOffsets(raw);
|
||||
const mappedNeedle = normalizeEvidenceText(needle);
|
||||
normalizedStart = mapped.text.indexOf(mappedNeedle);
|
||||
sourceOffsets = mapped.offsets;
|
||||
if (normalizedStart < 0) return null;
|
||||
start = sourceOffsets[normalizedStart];
|
||||
end = sourceOffsets[normalizedStart + mappedNeedle.length - 1] + 1;
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start) return null;
|
||||
const range = document.createRange();
|
||||
range.setStart(node, start);
|
||||
range.setEnd(node, end);
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('data-mnote-office-evidence-target', 'true');
|
||||
try {{
|
||||
range.surroundContents(span);
|
||||
return span;
|
||||
}} catch (_) {{
|
||||
return null;
|
||||
}}
|
||||
}}
|
||||
if (normalizedStart < 0) return null;
|
||||
start = sourceOffsets[normalizedStart];
|
||||
end = sourceOffsets[normalizedStart + compactNeedle.length - 1] + 1;
|
||||
}}
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start) return null;
|
||||
const range = document.createRange();
|
||||
range.setStart(node, start);
|
||||
range.setEnd(node, end);
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('data-mnote-office-evidence-target', 'true');
|
||||
try {{
|
||||
range.surroundContents(span);
|
||||
return span;
|
||||
}} catch (_) {{
|
||||
return null;
|
||||
}}
|
||||
}}
|
||||
|
||||
function markEvidenceRangeAcrossTextNodes(needle) {{
|
||||
if (!viewer) return false;
|
||||
const compactNeedle = normalizeEvidenceText(needle).replace(/\s+/g, '');
|
||||
if (!compactNeedle) return false;
|
||||
const refs = [];
|
||||
let compactText = '';
|
||||
const walker = document.createTreeWalker(viewer, NodeFilter.SHOW_TEXT);
|
||||
let node = walker.nextNode();
|
||||
while (node) {{
|
||||
const raw = String(node.textContent || '');
|
||||
for (let offset = 0; offset < raw.length; offset += 1) {{
|
||||
const ch = raw[offset];
|
||||
if (/\s/.test(ch)) continue;
|
||||
compactText += ch;
|
||||
refs.push({{ node, offset }});
|
||||
}}
|
||||
node = walker.nextNode();
|
||||
}}
|
||||
const startIndex = compactText.indexOf(compactNeedle);
|
||||
if (startIndex < 0) return false;
|
||||
const endIndex = startIndex + compactNeedle.length - 1;
|
||||
const startRef = refs[startIndex];
|
||||
const endRef = refs[endIndex];
|
||||
if (!startRef || !endRef) return false;
|
||||
const range = document.createRange();
|
||||
range.setStart(startRef.node, startRef.offset);
|
||||
range.setEnd(endRef.node, endRef.offset + 1);
|
||||
const rect = range.getBoundingClientRect();
|
||||
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
||||
let marker = viewer.querySelector('[data-mnote-office-evidence-marker="true"]');
|
||||
if (!(marker instanceof HTMLElement)) {{
|
||||
marker = document.createElement('div');
|
||||
marker.className = 'mnote-office-evidence-marker';
|
||||
marker.setAttribute('data-mnote-office-evidence-marker', 'true');
|
||||
viewer.append(marker);
|
||||
}}
|
||||
const viewerRect = viewer.getBoundingClientRect();
|
||||
marker.textContent = '';
|
||||
marker.setAttribute('data-mnote-office-evidence-target-text', normalizeEvidenceText(needle));
|
||||
marker.setAttribute('data-mnote-office-evidence-marker-mode', 'range');
|
||||
marker.style.left = Math.max(0, Math.round(rect.left - viewerRect.left + viewer.scrollLeft)).toString() + 'px';
|
||||
marker.style.top = Math.max(0, Math.round(rect.top - viewerRect.top + viewer.scrollTop)).toString() + 'px';
|
||||
marker.style.width = Math.max(8, Math.round(rect.width)).toString() + 'px';
|
||||
marker.style.height = Math.max(8, Math.round(rect.height)).toString() + 'px';
|
||||
marker.style.maxWidth = 'none';
|
||||
marker.style.padding = '0';
|
||||
return markEvidenceTarget(marker);
|
||||
}}
|
||||
|
||||
function pageForEvidenceBlock(sourceMap, block) {{
|
||||
if (!sourceMap || typeof sourceMap !== 'object' || !block) return null;
|
||||
const pages = Array.isArray(sourceMap.pages) ? sourceMap.pages : [];
|
||||
for (const page of pages) {{
|
||||
const blocks = Array.isArray(page && page.blocks) ? page.blocks : [];
|
||||
if (blocks.includes(block)) return page;
|
||||
}}
|
||||
return null;
|
||||
}}
|
||||
|
||||
function findEvidenceBlockInSourceMap(sourceMap) {{
|
||||
if (!sourceMap || typeof sourceMap !== 'object' || !evidenceBlockId) return null;
|
||||
const pages = Array.isArray(sourceMap.pages) ? sourceMap.pages : [];
|
||||
for (const page of pages) {{
|
||||
const blocks = Array.isArray(page && page.blocks) ? page.blocks : [];
|
||||
const block = blocks.find(item => String(item && (item.id || item.blockId || item.block_id) || '') === evidenceBlockId);
|
||||
if (block) return block;
|
||||
}}
|
||||
return null;
|
||||
}}
|
||||
|
||||
async function fetchEvidenceSourceMap() {{
|
||||
if (!evidenceSourceMapPath || !body.dataset.mnoteRootUri) return null;
|
||||
const params = new URLSearchParams();
|
||||
params.set('rootUri', body.dataset.mnoteRootUri);
|
||||
params.set('path', evidenceSourceMapPath);
|
||||
const response = await fetch('/api/local-folder/files/open?' + params.toString(), {{
|
||||
credentials: 'same-origin',
|
||||
headers: {{ accept: 'application/json, text/plain, */*' }}
|
||||
}});
|
||||
if (!response.ok) return null;
|
||||
return response.json().catch(() => null);
|
||||
}}
|
||||
|
||||
function scrollToEvidenceText(text) {{
|
||||
const needle = normalizeEvidenceText(text);
|
||||
if (!needle || !viewer) return false;
|
||||
viewer.querySelectorAll('[data-mnote-office-evidence-target="true"]').forEach(node => {{
|
||||
if (node instanceof HTMLElement) {{
|
||||
if (node.tagName === 'SPAN' && node.childNodes.length === 1 && node.firstChild instanceof Text) {{
|
||||
node.replaceWith(node.firstChild);
|
||||
}} else {{
|
||||
node.removeAttribute('data-mnote-office-evidence-target');
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
const walker = document.createTreeWalker(viewer, NodeFilter.SHOW_TEXT);
|
||||
let node = walker.nextNode();
|
||||
while (node) {{
|
||||
if (normalizeEvidenceText(node.textContent).includes(needle)) {{
|
||||
const inlineTarget = wrapEvidenceTextNode(node, needle);
|
||||
if (inlineTarget) return markEvidenceTarget(inlineTarget);
|
||||
const target = node.parentElement && node.parentElement.closest('p, div, span, table, section') || node.parentElement;
|
||||
if (target instanceof HTMLElement) {{
|
||||
const rect = target.getBoundingClientRect();
|
||||
if (rect.height > window.innerHeight * 1.8 || normalizeEvidenceText(target.textContent).length > 4000) break;
|
||||
}}
|
||||
return markEvidenceTarget(target);
|
||||
}}
|
||||
node = walker.nextNode();
|
||||
}}
|
||||
if (markEvidenceRangeAcrossTextNodes(needle)) return true;
|
||||
return false;
|
||||
}}
|
||||
|
||||
function scrollToEvidenceCoordinate(sourceMap, block) {{
|
||||
if (!viewer || !sourceMap || !block) return false;
|
||||
const page = pageForEvidenceBlock(sourceMap, block);
|
||||
const bbox = block.bbox && typeof block.bbox === 'object' ? block.bbox : null;
|
||||
const pageNumber = Number(page && page.page || evidencePage || 0);
|
||||
const pageCount = Math.max(1, Number(sourceMap.pageCount || (Array.isArray(sourceMap.pages) ? sourceMap.pages.length : 0)) || 1);
|
||||
if (!Number.isFinite(pageNumber) || pageNumber <= 0) return false;
|
||||
const renderedPages = Array.from(viewer.querySelectorAll('section.docx, section.mnote-docx, .pptx-preview-slide-wrapper'))
|
||||
.filter(node => node instanceof HTMLElement);
|
||||
const pageElement = renderedPages[pageNumber - 1];
|
||||
let top = 0;
|
||||
if (pageElement instanceof HTMLElement) {{
|
||||
const pageHeight = Math.max(1, pageElement.scrollHeight || pageElement.getBoundingClientRect().height || 1);
|
||||
const sourcePageHeight = Math.max(1, Number(page && page.height || 0) || pageHeight);
|
||||
top = pageElement.offsetTop + (bbox ? (Number(bbox.y0) / sourcePageHeight) * pageHeight : pageHeight / 2);
|
||||
}} else {{
|
||||
const contentHeight = Math.max(1, viewer.scrollHeight || document.documentElement.scrollHeight || 1);
|
||||
const estimatedPageHeight = contentHeight / pageCount;
|
||||
const sourcePageHeight = Math.max(1, Number(page && page.height || 0) || estimatedPageHeight);
|
||||
top = estimatedPageHeight * (pageNumber - 1) + (bbox ? (Number(bbox.y0) / sourcePageHeight) * estimatedPageHeight : estimatedPageHeight / 2);
|
||||
}}
|
||||
let marker = viewer.querySelector('[data-mnote-office-evidence-marker="true"]');
|
||||
if (!(marker instanceof HTMLElement)) {{
|
||||
marker = document.createElement('div');
|
||||
marker.className = 'mnote-office-evidence-marker';
|
||||
marker.setAttribute('data-mnote-office-evidence-marker', 'true');
|
||||
viewer.append(marker);
|
||||
}}
|
||||
marker.textContent = normalizeEvidenceText(block.text || evidenceBlockId || '命中位置');
|
||||
marker.removeAttribute('data-mnote-office-evidence-target-text');
|
||||
marker.setAttribute('data-mnote-office-evidence-marker-mode', 'estimated');
|
||||
marker.style.width = '';
|
||||
marker.style.height = '';
|
||||
marker.style.padding = '';
|
||||
marker.style.top = Math.max(0, Math.round(top)).toString() + 'px';
|
||||
return markEvidenceTarget(marker);
|
||||
}}
|
||||
|
||||
function scrollToEvidencePageFallback() {{
|
||||
if (!viewer || !Number.isFinite(evidencePage) || evidencePage <= 0) return false;
|
||||
const pages = Array.from(viewer.querySelectorAll('section.docx, section.mnote-docx, .pptx-preview-slide-wrapper'));
|
||||
const target = pages[Math.max(0, Math.min(pages.length - 1, evidencePage - 1))];
|
||||
return markEvidenceTarget(target);
|
||||
}}
|
||||
|
||||
async function applyEvidenceLocator() {{
|
||||
if (!viewer || (!evidencePage && !evidenceBlockId && !evidenceBbox)) return;
|
||||
try {{
|
||||
const sourceMap = await fetchEvidenceSourceMap();
|
||||
const block = findEvidenceBlockInSourceMap(sourceMap);
|
||||
if (block && scrollToEvidenceText(block.text)) return;
|
||||
if (scrollToEvidenceCoordinate(sourceMap, block)) return;
|
||||
}} catch (_) {{}}
|
||||
scrollToEvidencePageFallback();
|
||||
}}
|
||||
|
||||
function updateEvidenceLocator(locator) {{
|
||||
const next = locator && typeof locator === 'object' ? locator : {{}};
|
||||
evidencePage = Number(next.page || 0);
|
||||
evidenceBbox = String(next.bbox || '');
|
||||
evidenceSourceMapPath = String(next.sourceMapPath || '');
|
||||
evidenceBlockId = String(next.blockId || '');
|
||||
body.dataset.evidencePage = evidencePage ? String(evidencePage) : '';
|
||||
body.dataset.evidenceBbox = evidenceBbox;
|
||||
body.dataset.evidenceSourceMapPath = evidenceSourceMapPath;
|
||||
body.dataset.evidenceBlockId = evidenceBlockId;
|
||||
document.documentElement.removeAttribute('data-mnote-office-evidence-applied');
|
||||
void applyEvidenceLocator();
|
||||
}}
|
||||
|
||||
window.addEventListener('message', (event) => {{
|
||||
if (event.origin !== window.location.origin) return;
|
||||
const data = event.data && typeof event.data === 'object' ? event.data : {{}};
|
||||
if (data.type === 'mnote:office-evidence-locator') updateEvidenceLocator(data);
|
||||
}});
|
||||
|
||||
async function fetchArrayBuffer() {{
|
||||
const response = await fetch(fileUrl, {{ credentials: fileUrl.startsWith('/') ? 'same-origin' : 'include' }});
|
||||
if (!response.ok) throw new Error(fileReadErrorMessage(response.status));
|
||||
@@ -1321,6 +1637,7 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
else if (fileType === 'csv') await renderCsv();
|
||||
else if (fileType === 'pptx') await renderPptx();
|
||||
else showMessage('当前轻量预览 POC 暂不支持 .' + fileType + ',请用 OnlyOffice 打开。');
|
||||
await applyEvidenceLocator();
|
||||
setStatus('完成');
|
||||
}} catch (error) {{
|
||||
console.warn('[mnote office preview] render failed', error);
|
||||
@@ -1342,6 +1659,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
|
||||
source_kind = escape_html(&source_kind),
|
||||
root_uri = escape_html(&root_uri),
|
||||
document_id = escape_html(&document_id),
|
||||
target_page = escape_html(&target_page),
|
||||
target_bbox = escape_html(&target_bbox),
|
||||
target_source_map_path = escape_html(&target_source_map_path),
|
||||
target_block_id = escape_html(&target_block_id),
|
||||
);
|
||||
let mut response = Html(html).into_response();
|
||||
stamp_shell_headers(response.headers_mut(), "office-preview");
|
||||
@@ -3310,6 +3631,12 @@ mod tests {
|
||||
let resource_runtime = DOCUMENT_RESOURCE_TAB_RUNTIME_JS;
|
||||
assert!(runtime.contains("document-resource-tab-runtime.js"));
|
||||
assert!(resource_runtime.contains("resourceTabBadgeKind(input, kind)"));
|
||||
assert!(resource_runtime.contains("后台任务"));
|
||||
assert!(resource_runtime.contains("data-mnote-local-ocr-task-tab"));
|
||||
assert!(resource_runtime.contains("data-mnote-local-ocr-task-clear-completed"));
|
||||
assert!(resource_runtime.contains("role=\"progressbar\""));
|
||||
assert!(resource_runtime.contains("localOcrTaskCategory(job)"));
|
||||
assert!(resource_runtime.contains("localOcrTaskProgress(job)"));
|
||||
assert!(resource_runtime.contains("mnote.open_editors_snapshot.v1"));
|
||||
assert!(runtime.contains("getOpenEditorsSnapshot"));
|
||||
assert!(resource_runtime.contains("bindMainEditorTabStrip"));
|
||||
@@ -3361,6 +3688,8 @@ mod tests {
|
||||
assert!(resource_runtime.contains("data-mnote-evidence-bbox"));
|
||||
assert!(resource_runtime.contains("data-mnote-evidence-text-highlight"));
|
||||
assert!(runtime.contains("applyDocumentEvidenceLocatorFromUrl"));
|
||||
assert!(runtime.contains("sourceMapPath: String(url.searchParams.get('sourceMapPath')"));
|
||||
assert!(runtime.contains("blockId: String(url.searchParams.get('blockId')"));
|
||||
let sidebar_runtime = SIDEBAR_TREE_RUNTIME_JS;
|
||||
assert!(sidebar_runtime.contains("openEvidenceSearchResult"));
|
||||
assert!(sidebar_runtime.contains("data-evidence-locator"));
|
||||
|
||||
Reference in New Issue
Block a user