fix local office resource editing
- add local-folder OnlyOffice sign/callback writeback and edit-tab handling - align main resource tabs, attachment edit menu, slash isolation, and filetree context behavior - record Sidex/Hermes gap reviews and Reasonix task checklists
This commit is contained in:
@@ -1253,6 +1253,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var treeView = normalizeSidebarTreeMode(options && options.treeView ? options.treeView : activeSidebarTreeMode());
|
||||
persistSidebarTreeMode(treeView);
|
||||
if (currentDocumentId() === nodeId && window.location.pathname.indexOf('/documents/') === 0) {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.activatePageTab === 'function') {
|
||||
window.__mnoteDocumentPaneRuntime.activatePageTab();
|
||||
}
|
||||
document.querySelectorAll('.tree-row[data-active="true"], .tree-row[data-selected="true"]').forEach(function(row) {
|
||||
if (row instanceof HTMLElement) {
|
||||
row.setAttribute('data-active', 'false');
|
||||
@@ -1268,6 +1271,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
}
|
||||
});
|
||||
selectSidebarFileTreeDocument(nodeId, { scrollIntoView: true });
|
||||
return;
|
||||
}
|
||||
var targetUrl = new URL('/documents/' + encodeURIComponent(nodeId), window.location.origin);
|
||||
@@ -1289,6 +1293,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}).then(function(){
|
||||
if (mnoteNavigationInFlight === url) mnoteNavigationInFlight = '';
|
||||
document.documentElement.removeAttribute('data-mnote-navigation-pending');
|
||||
selectSidebarFileTreeDocument(nodeId, { scrollIntoView: true });
|
||||
}).catch(function(error){
|
||||
console.warn('mnote pane 内导航失败,将回退整页导航', error);
|
||||
if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') {
|
||||
@@ -1323,9 +1328,32 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
window.__mnoteLocalFolderSelfChangeSuppressions = window.__mnoteLocalFolderSelfChangeSuppressions || new Map();
|
||||
window.__mnoteLocalFolderSelfChangeSuppressions.set(nextDocumentId, Date.now() + 5000);
|
||||
}
|
||||
if (nextDocumentId && currentSourceKind() === 'local_folder') {
|
||||
await refreshLocalFolderSidebarSnapshot();
|
||||
selectSidebarFileTreeDocument(nextDocumentId, { scrollIntoView: true });
|
||||
document.documentElement.setAttribute('data-mnote-create-page-selected-document-id', nextDocumentId);
|
||||
}
|
||||
navigateToDocument(nextDocumentId, nextWorkspaceId, { treeView: activeSidebarTreeMode() });
|
||||
}
|
||||
|
||||
async function createFileTreeFolder(trigger, parentId) {
|
||||
if (currentSourceKind() !== 'local_folder') return false;
|
||||
var workspaceId = resolveWorkspaceId(trigger || document.body);
|
||||
var effectiveParentId = String(parentId || '').trim();
|
||||
var title = window.prompt('新建文件夹', '新建文件夹');
|
||||
if (!title || !title.trim()) return false;
|
||||
var result = await dispatchTreeCommand(trigger || document.body, {
|
||||
action: 'create_folder',
|
||||
workspaceId: workspaceId,
|
||||
parentId: effectiveParentId || null,
|
||||
title: title.trim()
|
||||
});
|
||||
document.documentElement.setAttribute('data-mnote-filetree-folder-created', 'true');
|
||||
document.documentElement.setAttribute('data-mnote-filetree-folder-created-id', commandDocumentId(result, result.id || ''));
|
||||
void refreshLocalFolderSidebarSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
function applySidebarTreeTab(mode, shell) {
|
||||
mode = persistSidebarTreeMode(mode);
|
||||
shell = shell || document.querySelector('[data-testid="wolai-sidebar-page-tree-shell"]');
|
||||
@@ -2063,7 +2091,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (input.assetId) target.searchParams.set('assetId', input.assetId);
|
||||
if (input.documentId) target.searchParams.set('documentId', input.documentId);
|
||||
if (input.userId) target.searchParams.set('userId', input.userId);
|
||||
target.searchParams.set('mode', input.mode || 'edit');
|
||||
target.searchParams.set('mode', input.mode || 'view');
|
||||
return target.toString();
|
||||
}
|
||||
|
||||
@@ -2075,7 +2103,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (input.assetId) params.set('assetId', input.assetId);
|
||||
if (input.documentId) params.set('documentId', input.documentId);
|
||||
if (input.userId) params.set('userId', input.userId);
|
||||
params.set('mode', input.mode || 'edit');
|
||||
params.set('mode', input.mode || 'view');
|
||||
if (input.documentId && input.assetId) {
|
||||
return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId) + '?' + params.toString();
|
||||
}
|
||||
@@ -2094,7 +2122,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId || ('local-file:' + relativePath),
|
||||
documentId: documentId || currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: mode || 'edit'
|
||||
mode: mode || 'view'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2224,6 +2252,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (!assetId) return;
|
||||
var openTarget = String(detail && detail.openTarget || '').trim().toLowerCase();
|
||||
var forceNewWindow = openTarget === 'new-window';
|
||||
var forceEditMode = openTarget === 'edit-mode';
|
||||
if (openTarget === 'side') {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceAsSideTarget === 'function') {
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceAsSideTarget({
|
||||
@@ -2261,7 +2290,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
navigateToMindmapObject(String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, String(detail.workspaceId || '').trim());
|
||||
return;
|
||||
}
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, forceNewWindow ? 'edit' : 'view');
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, forceEditMode ? 'edit' : 'view');
|
||||
if (localOfficeUrl) {
|
||||
if (!forceNewWindow && await openLocalResourceInActiveTab({
|
||||
path: localFilePath,
|
||||
@@ -2328,15 +2357,29 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var fileType = inferOnlyOfficeFileType(fileName, asset.mime_type);
|
||||
if (fileType) {
|
||||
var userId = await fetchCurrentOnlyOfficeUserId();
|
||||
window.open(buildOnlyOfficeOpenUrl({
|
||||
var officeUrl = buildOnlyOfficeOpenUrl({
|
||||
fileUrl: fileUrl,
|
||||
fileName: fileName,
|
||||
fileType: fileType,
|
||||
assetId: assetId,
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
userId: userId,
|
||||
mode: 'edit'
|
||||
}), '_blank', 'noopener,noreferrer');
|
||||
mode: forceEditMode ? 'edit' : 'view'
|
||||
});
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
var didOpen = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:onlyoffice:' + String(asset.document_id || detail.documentId || '').trim() + ':' + assetId,
|
||||
assetId: assetId,
|
||||
title: fileName,
|
||||
fileName: fileName,
|
||||
kind: 'office',
|
||||
officeUrl: officeUrl,
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
});
|
||||
if (didOpen) return;
|
||||
}
|
||||
window.open(officeUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
if (isCodeAttachmentFileName(fileName) && String(asset.document_id || detail.documentId || '').trim() === currentDocumentId()) {
|
||||
@@ -2695,7 +2738,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId,
|
||||
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
||||
userId: userId || '',
|
||||
mode: 'edit'
|
||||
mode: 'view'
|
||||
});
|
||||
}
|
||||
return buildOnlyOfficeOpenUrl({
|
||||
@@ -2705,7 +2748,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId,
|
||||
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
||||
userId: userId || '',
|
||||
mode: 'edit'
|
||||
mode: 'view'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2803,7 +2846,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: detail.assetId,
|
||||
documentId: detail.documentId || currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: 'edit'
|
||||
mode: 'view'
|
||||
}));
|
||||
link.setAttribute('data-mnote-attachment-link', 'true');
|
||||
link.setAttribute('data-asset-id', detail.assetId);
|
||||
@@ -3172,7 +3215,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId,
|
||||
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
||||
userId: userId || '',
|
||||
mode: 'edit'
|
||||
mode: 'view'
|
||||
}))
|
||||
: href;
|
||||
var inserted = editor.chain().focus().insertContent({
|
||||
@@ -3557,6 +3600,57 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
});
|
||||
}
|
||||
|
||||
function fileTreeCopyPath(detail, trigger) {
|
||||
// 对 local_folder 复制真实相对路径,不复制标题
|
||||
if (currentSourceKind() === 'local_folder') {
|
||||
if (detail.assetId) {
|
||||
var path = localFilePathFromAssetId(detail.assetId);
|
||||
if (path) {
|
||||
try {
|
||||
return decodeURIComponent(path.replace(/~2F/g, '/'));
|
||||
} catch (_) {
|
||||
return path.replace(/~2F/g, '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (detail.documentId) {
|
||||
var docPath = String(detail.documentId || '')
|
||||
.replace(/^local-md:/, '')
|
||||
.replace(/^local-dir:/, '')
|
||||
.replace(/~2F/g, '/');
|
||||
if (docPath) {
|
||||
try {
|
||||
return decodeURIComponent(docPath);
|
||||
} catch (_) {
|
||||
return docPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return detail.title || '';
|
||||
}
|
||||
|
||||
function fileTreeMenuTargetParentId(detail, trigger) {
|
||||
var rowKind = String(detail && detail.rowKind || '').trim();
|
||||
var rowId = String(detail && detail.rowId || '').trim();
|
||||
var documentId = String(detail && detail.documentId || '').trim();
|
||||
if (rowKind === 'folder' && rowId) return rowId;
|
||||
if (rowKind === 'directory' && rowId) return rowId;
|
||||
if (documentId) return documentId;
|
||||
if (trigger && typeof trigger.getAttribute === 'function') {
|
||||
var triggerKind = String(trigger.getAttribute('data-row-kind') || '').trim();
|
||||
var triggerRowId = String(trigger.getAttribute('data-row-id') || '').trim();
|
||||
if ((triggerKind === 'folder' || triggerKind === 'directory') && triggerRowId) return triggerRowId;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function withOfficeEditModeGuard(callback) {
|
||||
document.documentElement.setAttribute('data-mnote-last-office-edit-mode-requested', 'true');
|
||||
document.documentElement.setAttribute('data-mnote-last-office-edit-mode-guard', 'silent');
|
||||
callback();
|
||||
}
|
||||
|
||||
function handleTreeContextMenuAction(action, detail, trigger) {
|
||||
closeTreeContextMenu();
|
||||
detail = detail || {};
|
||||
@@ -3577,6 +3671,18 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
openEditorAttachmentNewWindow(detail);
|
||||
return;
|
||||
}
|
||||
if (action === 'new-window-edit') {
|
||||
withOfficeEditModeGuard(function() {
|
||||
openEditorAttachmentNewWindow(detail, 'edit');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'open-edit-mode') {
|
||||
withOfficeEditModeGuard(function() {
|
||||
void openEditorAttachmentEditTab(detail);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'right-preview') {
|
||||
dispatchSidebarEvent('tree.attachment.open-right', detail);
|
||||
return;
|
||||
@@ -3597,6 +3703,13 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
|
||||
return;
|
||||
}
|
||||
if (isAsset && action === 'open-edit-mode') {
|
||||
withOfficeEditModeGuard(function() {
|
||||
recordFileTreeAction('open-edit-mode', detail);
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'edit-mode' });
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (isAsset && action === 'open-right') {
|
||||
recordFileTreeAction('open-right', detail);
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'side' });
|
||||
@@ -3645,7 +3758,17 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
if (action === 'new-file') {
|
||||
void createPage(trigger || document.body, documentId || null);
|
||||
void createPage(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null);
|
||||
return;
|
||||
}
|
||||
if (action === 'new-folder') {
|
||||
recordFileTreeAction('new-folder', detail);
|
||||
void createFileTreeFolder(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null).then(function(ok) {
|
||||
recordFileTreeActionStatus(ok ? 'created' : 'skipped', detail);
|
||||
}).catch(function(error) {
|
||||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||||
window.alert(error && error.message ? error.message : '新建文件夹失败');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'paste-into') {
|
||||
@@ -3660,7 +3783,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
if (action === 'copy-path') {
|
||||
void copyTreeContextValue(title, 'copy-path');
|
||||
void copyTreeContextValue(fileTreeCopyPath(detail, trigger), 'copy-path');
|
||||
return;
|
||||
}
|
||||
if (action === 'refresh') {
|
||||
@@ -3790,6 +3913,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ separator: true },
|
||||
{ action: 'popup-preview', icon: 'preview', label: '弹窗预览' },
|
||||
{ action: 'right-preview', icon: 'right_panel_open', label: '右侧预览' },
|
||||
{ action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' },
|
||||
{ action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' },
|
||||
{ action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' },
|
||||
{ action: 'download', icon: 'download', label: '下载' },
|
||||
{ action: 'replace-file', icon: 'sync', label: '更换文件' },
|
||||
@@ -3799,7 +3924,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ separator: true },
|
||||
{ action: 'color', icon: 'format_paint', label: '颜色' }
|
||||
] : isAsset ? [
|
||||
{ action: 'open-right', icon: 'open_in_new', label: '在右侧边栏打开', shortcut: 'Alt + O' },
|
||||
{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' },
|
||||
{ action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2' },
|
||||
{ action: 'copy-id', icon: 'tag', label: '复制资源 ID' },
|
||||
@@ -3817,14 +3942,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ action: 'copy-path', icon: 'content_copy', label: 'Copy Path' },
|
||||
{ separator: true },
|
||||
{ action: 'new-file', icon: 'note_add', label: 'New File' },
|
||||
{ action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: true, title: 'Convex 文件夹对象尚未进入正式 tree command' },
|
||||
{ action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: currentSourceKind() !== 'local_folder', title: currentSourceKind() === 'local_folder' ? '在当前目录下创建子文件夹' : '仅 local folder 支持创建文件夹' },
|
||||
{ action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !sidebarFileTreeClipboard, title: sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空' },
|
||||
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
|
||||
{ action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
|
||||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' },
|
||||
{ separator: true },
|
||||
{ action: 'duplicate', icon: 'file_copy', label: '拷贝副本' },
|
||||
{ separator: true },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2' },
|
||||
{ separator: true },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true }
|
||||
@@ -3835,8 +3958,6 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' },
|
||||
{ action: 'copy-id', icon: 'tag', label: '复制页面ID' },
|
||||
{ separator: true },
|
||||
{ action: 'duplicate', icon: 'file_copy', label: '拷贝副本' },
|
||||
{ separator: true },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名' },
|
||||
{ separator: true },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true }
|
||||
@@ -3929,6 +4050,33 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return Array.from(selected);
|
||||
}
|
||||
|
||||
function selectSidebarFileTreeDocument(documentId, options) {
|
||||
var id = String(documentId || '').trim();
|
||||
if (!id) return false;
|
||||
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + id) + '"]')
|
||||
|| document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="' + cssEscape(id) + '"]')
|
||||
|| document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-doc-id="' + cssEscape(id) + '"]');
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
var rowId = row.getAttribute('data-row-id') || '';
|
||||
if (!rowId) return false;
|
||||
sidebarFileTreeSelection.selectedRowIds = new Set([rowId]);
|
||||
sidebarFileTreeSelection.anchorRowId = rowId;
|
||||
sidebarFileTreeSelection.focusedRowId = rowId;
|
||||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) {
|
||||
if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false');
|
||||
});
|
||||
syncSidebarFileTreeSelection();
|
||||
row.setAttribute('data-active', 'true');
|
||||
if (options && options.scrollIntoView !== false) {
|
||||
try {
|
||||
row.scrollIntoView({ block: 'nearest' });
|
||||
} catch (_) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function selectedSidebarFileTreeRowIdsForDrag(row) {
|
||||
var rowId = row instanceof HTMLElement ? row.getAttribute('data-row-id') || '' : '';
|
||||
if (rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId)) {
|
||||
@@ -4773,6 +4921,22 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
function pageAiResolvePermission(permissionId, decision) {
|
||||
permissionId = String(permissionId || '').trim();
|
||||
if (!permissionId) return;
|
||||
// 调用后端 resolve-permission 端点,让 ACP agent 得到真实响应
|
||||
var runId = pageUiState.pageAiCurrentRunId;
|
||||
if (runId) {
|
||||
fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/resolve-permission', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ permissionId: permissionId, decision: decision })
|
||||
}).then(function(response) {
|
||||
if (!response.ok) console.warn('resolve-permission 后端返回异常', response.status);
|
||||
}).catch(function(err) {
|
||||
console.warn('resolve-permission 请求失败', err);
|
||||
});
|
||||
} else {
|
||||
console.warn('resolve-permission: 无活跃 runId,只能本地更新');
|
||||
}
|
||||
// 本地乐观更新 UI
|
||||
pageUiState.pageAiMessages.forEach(function(item) {
|
||||
if (item.kind === 'permission' && item.permissionId === permissionId) {
|
||||
item.resolved = true;
|
||||
@@ -4986,6 +5150,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
if (eventType === 'tool.started' || eventType === 'tool.completed' || eventType === 'tool.failed') {
|
||||
var toolName = String(payload.toolName || payload.tool || payload.name || eventType).trim();
|
||||
var rawLocations = payload.locations;
|
||||
return {
|
||||
role: 'tool',
|
||||
content: toolName,
|
||||
@@ -4995,6 +5160,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
status: eventType === 'tool.completed' ? 'completed' : (eventType === 'tool.failed' ? 'failed' : 'running'),
|
||||
argsSummary: pageAiPreviewValue(payload.args || payload.arguments || payload.input),
|
||||
resultSummary: pageAiPreviewValue(payload.summary || payload.result || payload.output || payload.error),
|
||||
locations: Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [],
|
||||
traceId: String(payload.traceId || payload.trace_id || ''),
|
||||
auditId: String(payload.auditId || payload.audit_id || '')
|
||||
};
|
||||
@@ -5610,6 +5776,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
traceId: traceId,
|
||||
auditId: auditId
|
||||
};
|
||||
var rawLocations = toolEvent && toolEvent.locations;
|
||||
var locations = Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [];
|
||||
var existing = pageUiState.pageAiMessages.find(function(item) {
|
||||
return item.role === 'tool' && item.toolCallId === toolCallId;
|
||||
});
|
||||
@@ -5624,6 +5792,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
status: status,
|
||||
argsSummary: '',
|
||||
resultSummary: '',
|
||||
locations: locations,
|
||||
traceId: traceId,
|
||||
auditId: auditId
|
||||
};
|
||||
@@ -5637,6 +5806,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
existing.auditId = auditId || existing.auditId || '';
|
||||
if (argsSummary) existing.argsSummary = argsSummary;
|
||||
if (resultSummary) existing.resultSummary = resultSummary;
|
||||
if (locations.length) existing.locations = locations;
|
||||
if (status === 'completed') {
|
||||
pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId);
|
||||
}
|
||||
@@ -6585,7 +6755,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
|
||||
if (item.role === 'tool') {
|
||||
var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中');
|
||||
var locationRows = Array.isArray(item.locations) && item.locations.length
|
||||
? '<div class="wolai-page-ai-tool-meta">位置:' + item.locations.map(function(loc, idx) {
|
||||
return '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:8px">' +
|
||||
'<span>' + escapeHtml(loc) + '</span>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" style="font-size:0.85em;padding:0 4px;min-width:unset" data-page-ai-open-location="' + escapeHtml(loc) + '" data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" title="打开文件">打开</button>' +
|
||||
'</span>';
|
||||
}).join('') + '</div>'
|
||||
: '';
|
||||
var detailRows = [
|
||||
locationRows,
|
||||
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
|
||||
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
|
||||
item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
|
||||
@@ -6629,6 +6808,21 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
if (item.kind === 'plan') {
|
||||
var planEntries = Array.isArray(item.entries) ? item.entries : [];
|
||||
var listHtml = planEntries.map(function(entry, idx) {
|
||||
return '<li style="margin:2px 0">' + escapeHtml(String(entry || '')) + '</li>';
|
||||
}).join('');
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-plan="true">' +
|
||||
'<details class="wolai-page-ai-plan-details" open>' +
|
||||
'<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.8;font-size:0.85em">执行计划 · ' + planEntries.length + ' 步</summary>' +
|
||||
'<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.75;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' +
|
||||
'<ol style="margin:4px 0;padding-left:20px">' + listHtml + '</ol>' +
|
||||
'</div>' +
|
||||
'</details>' +
|
||||
'</div>';
|
||||
}
|
||||
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
|
||||
@@ -7046,6 +7240,40 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
pageUiState.pageAiStoppedRunIds[runId] = true;
|
||||
pageAiSetRunStatus('aborted', runId);
|
||||
}
|
||||
if (eventName === 'session.info.updated') {
|
||||
try {
|
||||
var infoPayload = JSON.parse(payloadText || 'null') || {};
|
||||
var newTitle = String(infoPayload.title || '').trim();
|
||||
if (newTitle) {
|
||||
var sessionForTitle = pageAiCurrentSession();
|
||||
if (sessionForTitle) {
|
||||
sessionForTitle.title = newTitle;
|
||||
sessionForTitle.updatedAt = Date.now();
|
||||
pageAiPersistSessions();
|
||||
renderPageAiControls();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName === 'plan.updated') {
|
||||
try {
|
||||
var planPayload = JSON.parse(payloadText || 'null') || {};
|
||||
var planEntries = Array.isArray(planPayload.entries) ? planPayload.entries : [];
|
||||
if (planEntries.length) {
|
||||
var planMsgs = pageUiState.pageAiMessages;
|
||||
var existingPlan = planMsgs.length > 0 && planMsgs[planMsgs.length - 1].kind === 'plan' ? planMsgs[planMsgs.length - 1] : null;
|
||||
if (existingPlan) {
|
||||
existingPlan.entries = planEntries;
|
||||
existingPlan.updatedAt = Date.now();
|
||||
} else {
|
||||
planMsgs.push({ role: 'system', kind: 'plan', entries: planEntries, createdAt: Date.now(), updatedAt: Date.now() });
|
||||
}
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
pageAiPersistSessions();
|
||||
renderPageAiConversation();
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
||||
pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
@@ -7424,7 +7652,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: url.searchParams.get('assetId') || '',
|
||||
documentId: url.searchParams.get('documentId') || currentDocumentId() || '',
|
||||
userId: url.searchParams.get('userId') || '',
|
||||
mode: url.searchParams.get('mode') || 'edit'
|
||||
mode: url.searchParams.get('mode') || 'view'
|
||||
});
|
||||
} catch (_) {
|
||||
return String(href || '');
|
||||
@@ -7453,7 +7681,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId,
|
||||
documentId: currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: 'edit'
|
||||
mode: 'view'
|
||||
});
|
||||
} else if (isOnlyOfficeAttachmentHref(rawHref)) {
|
||||
href = normalizeOnlyOfficeAttachmentHref(rawHref);
|
||||
@@ -7494,7 +7722,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
assetId: assetId,
|
||||
documentId: params.get('documentId') || currentDocumentId() || '',
|
||||
userId: params.get('userId') || '',
|
||||
mode: params.get('mode') || 'edit'
|
||||
mode: params.get('mode') || 'view'
|
||||
}));
|
||||
}
|
||||
attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) {
|
||||
@@ -7575,20 +7803,110 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
void openCodeEditorAttachment(detail);
|
||||
return;
|
||||
}
|
||||
var fileType = String(detail.fileType || '').trim();
|
||||
if (fileType && typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:onlyoffice:' + (detail.documentId || '') + ':' + (detail.assetId || ''),
|
||||
assetId: detail.assetId || '',
|
||||
title: detail.fileName || '附件',
|
||||
fileName: detail.fileName || '附件',
|
||||
kind: 'office',
|
||||
officeUrl: detail.href,
|
||||
documentId: detail.documentId || '',
|
||||
workspaceId: detail.workspaceId || ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
window.open(detail.href, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
function openEditorAttachmentNewWindow(detail) {
|
||||
function openEditorAttachmentNewWindow(detail, mode) {
|
||||
if (!detail) return;
|
||||
var requestedMode = mode === 'edit' ? 'edit' : 'view';
|
||||
var localFilePath = localFilePathFromAssetId(detail.assetId);
|
||||
if (localFilePath) {
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, 'edit');
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, requestedMode);
|
||||
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
|
||||
window.open(localOfficeUrl || localFileUrl || detail.href, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
window.open(detail.href || detail.fileUrl, '_blank', 'noopener,noreferrer');
|
||||
var href = detail.href || detail.fileUrl;
|
||||
if (detail.fileType && isOnlyOfficeAttachmentHref(href)) {
|
||||
try {
|
||||
var url = new URL(href, window.location.origin);
|
||||
url.searchParams.set('mode', requestedMode);
|
||||
href = url.toString();
|
||||
} catch (_) {}
|
||||
} else if (detail.fileType) {
|
||||
href = buildOnlyOfficeOpenUrl({
|
||||
fileUrl: detail.fileUrl || href || '',
|
||||
fileName: detail.fileName || '未命名附件',
|
||||
fileType: detail.fileType,
|
||||
assetId: detail.assetId || '',
|
||||
documentId: detail.documentId || currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: requestedMode
|
||||
});
|
||||
}
|
||||
window.open(href, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
async function openEditorAttachmentEditTab(detail) {
|
||||
if (!detail) return false;
|
||||
var localFilePath = localFilePathFromAssetId(detail.assetId);
|
||||
if (localFilePath) {
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, 'edit');
|
||||
if (localOfficeUrl) {
|
||||
var didOpenLocalEditTab = await openLocalResourceInActiveTab({
|
||||
path: localFilePath,
|
||||
title: detail.fileName || localFileName,
|
||||
kind: 'office',
|
||||
assetId: detail.assetId,
|
||||
documentId: detail.documentId || currentDocumentId() || '',
|
||||
workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '',
|
||||
href: buildLocalFileOpenUrl(localFilePath, false),
|
||||
officeUrl: localOfficeUrl
|
||||
});
|
||||
if (!didOpenLocalEditTab) window.open(localOfficeUrl, '_blank', 'noopener,noreferrer');
|
||||
return didOpenLocalEditTab;
|
||||
}
|
||||
}
|
||||
var href = detail.href || detail.fileUrl;
|
||||
if (detail.fileType && isOnlyOfficeAttachmentHref(href)) {
|
||||
try {
|
||||
var url = new URL(href, window.location.origin);
|
||||
url.searchParams.set('mode', 'edit');
|
||||
href = url.toString();
|
||||
} catch (_) {}
|
||||
} else if (detail.fileType) {
|
||||
href = buildOnlyOfficeOpenUrl({
|
||||
fileUrl: detail.fileUrl || href || '',
|
||||
fileName: detail.fileName || '未命名附件',
|
||||
fileType: detail.fileType,
|
||||
assetId: detail.assetId || '',
|
||||
documentId: detail.documentId || currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: 'edit'
|
||||
});
|
||||
}
|
||||
if (detail.fileType && typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
var didOpenEditTab = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:onlyoffice:' + (detail.documentId || '') + ':' + (detail.assetId || ''),
|
||||
assetId: detail.assetId || '',
|
||||
title: detail.fileName || '附件',
|
||||
fileName: detail.fileName || '附件',
|
||||
kind: 'office',
|
||||
officeUrl: href,
|
||||
documentId: detail.documentId || '',
|
||||
workspaceId: detail.workspaceId || ''
|
||||
});
|
||||
if (!didOpenEditTab && href) window.open(href, '_blank', 'noopener,noreferrer');
|
||||
return didOpenEditTab;
|
||||
}
|
||||
if (href) window.open(href, '_blank', 'noopener,noreferrer');
|
||||
return false;
|
||||
}
|
||||
|
||||
async function resolveEditorAttachmentUrl(detail) {
|
||||
@@ -7999,6 +8317,21 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
|
||||
var pageAiOpenLocation = closestAction(e.target, '[data-page-ai-open-location]');
|
||||
if (pageAiOpenLocation) {
|
||||
e.preventDefault();
|
||||
var loc = String(pageAiOpenLocation.getAttribute('data-page-ai-open-location') || '').trim();
|
||||
if (loc) {
|
||||
openLocalResourceInActiveTab({ path: loc }).then(function(opened) {
|
||||
if (!opened) {
|
||||
var href = buildLocalFileOpenUrl(loc, false);
|
||||
if (href) window.open(href, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var pageAiSession = closestAction(e.target, '[data-page-ai-session]');
|
||||
if (pageAiSession) {
|
||||
e.preventDefault();
|
||||
@@ -8181,10 +8514,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
var tree = document.getElementById('sidebar-tree-root');
|
||||
if (!tree || !tree.contains(e.target)) return;
|
||||
var pageRow = closestAction(e.target, '.tree-row[data-shell-mode="page"]');
|
||||
var btn = closestAction(e.target, '[data-rust-action]');
|
||||
if (!btn) return;
|
||||
var nodeId = btn.getAttribute('data-node-id');
|
||||
var action = btn.getAttribute('data-rust-action');
|
||||
if (!btn && !pageRow) return;
|
||||
var nodeId = (btn && btn.getAttribute('data-node-id')) || (pageRow && pageRow.getAttribute('data-node-id')) || '';
|
||||
var action = btn ? btn.getAttribute('data-rust-action') : 'open';
|
||||
|
||||
if (action === 'toggle') {
|
||||
var row = btn.closest('.tree-row');
|
||||
@@ -8192,11 +8526,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
toggleChildren(row, btn);
|
||||
e.preventDefault();
|
||||
} else if (action === 'open') {
|
||||
if (btn.getAttribute('data-page-openable') === 'false') {
|
||||
var openTrigger = btn || pageRow;
|
||||
if (openTrigger && openTrigger.getAttribute('data-page-openable') === 'false') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
var workspaceId = resolveWorkspaceId(btn);
|
||||
var workspaceId = resolveWorkspaceId(openTrigger);
|
||||
navigateToDocument(nodeId, workspaceId, { treeView: 'page' });
|
||||
e.preventDefault();
|
||||
} else if (action === 'create') {
|
||||
@@ -8714,6 +9049,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
sidebarFileTreeSelection.focusedRowId = rowId;
|
||||
});
|
||||
syncSidebarFileTreeSelection();
|
||||
window.addEventListener('mnote:primary-document-activated', function(event) {
|
||||
var detail = event && event.detail ? event.detail : {};
|
||||
selectSidebarFileTreeDocument(detail.documentId, { scrollIntoView: false });
|
||||
});
|
||||
restoreSidebarTreeTab();
|
||||
startLocalFolderSidebarWatch();
|
||||
})();
|
||||
@@ -9162,7 +9501,7 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/auth/whoami"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("buildOnlyOfficeOpenUrl"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("target.searchParams.set('userId'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("window.open(buildOnlyOfficeOpenUrl"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("window.open(officeUrl,"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("tree.filetree.internal-drop"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("tree.filetree.external-drop"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("beginFileTreeInlineRename"));
|
||||
@@ -9240,6 +9579,22 @@ mod tests {
|
||||
!SIDEBAR_TREE_JS.contains("(item.resolved ? ' disabled' : '')"),
|
||||
"已决 ACP permission 事件不能继续展示假审批按钮"
|
||||
);
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("session.info.updated"),
|
||||
"ACP SessionInfoUpdate 事件应通过 session.info.updated SSE 转发到前端"
|
||||
);
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("plan.updated"),
|
||||
"ACP PlanUpdate 事件应通过 plan.updated SSE 转发到前端"
|
||||
);
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("data-page-ai-plan"),
|
||||
"plan 消息应渲染为 data-page-ai-plan 标记的轻量系统状态面板"
|
||||
);
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("执行计划 · "),
|
||||
"plan 面板标题应显示执行计划和步数"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -9358,6 +9713,18 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("mnote-tree-context-menu"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("复制访问链接"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("删除到垃圾桶"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function createFileTreeFolder"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("action: 'create_folder'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function fileTreeMenuTargetParentId"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("if (action === 'new-folder')"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("fileTreeCopyPath(detail, trigger)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
"var pageRow = closestAction(e.target, '.tree-row[data-shell-mode=\"page\"]');"
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("var action = btn ? btn.getAttribute('data-rust-action') : 'open';"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("var openTrigger = btn || pageRow;"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("var workspaceId = resolveWorkspaceId(openTrigger);"));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#".tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"] > .tree-link > .tree-link-title"#
|
||||
));
|
||||
@@ -9491,6 +9858,24 @@ mod tests {
|
||||
"return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId)"
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains("return '/onlyoffice?' + params.toString();"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("target.searchParams.set('mode', input.mode || 'view');"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("params.set('mode', input.mode || 'view');"));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
"{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }"
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("{ action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' }"));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("{ action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' }"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function withOfficeEditModeGuard(callback)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-last-office-edit-mode-requested"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function openEditorAttachmentEditTab(detail)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("void openEditorAttachmentEditTab(detail);"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, 'edit')"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("openEditorAttachmentNewWindow(detail, 'edit')"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("forceEditMode ? 'edit' : 'view'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("url.searchParams.set('mode', requestedMode);"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("mode: requestedMode"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user