2026-05-26 01:31:28 +08:00
|
|
|
|
import { createSidebarWorkspaceRuntime } from './sidebar-workspace-runtime.js';
|
2026-05-26 02:35:08 +08:00
|
|
|
|
import { createSidebarPageTreeRuntime } from './sidebar-page-tree-runtime.js';
|
2026-05-26 01:51:07 +08:00
|
|
|
|
import { createSidebarTreeLiveApplyRuntime } from './sidebar-tree-live-apply-runtime.js';
|
2026-05-26 01:58:00 +08:00
|
|
|
|
import { createSidebarFileTreeOpenRuntime } from './sidebar-filetree-open-runtime.js';
|
2026-05-26 02:05:36 +08:00
|
|
|
|
import { createSidebarAttachmentOpenRuntime } from './sidebar-attachment-open-runtime.js';
|
2026-05-26 02:20:46 +08:00
|
|
|
|
import { createSidebarFileTreeCommandRuntime } from './sidebar-filetree-command-runtime.js';
|
2026-05-26 02:27:08 +08:00
|
|
|
|
import { createSidebarFileTreeUploadRuntime } from './sidebar-filetree-upload-runtime.js';
|
2026-05-26 04:01:52 +08:00
|
|
|
|
import { createSidebarPageAiRuntime } from './sidebar-page-ai-runtime.js';
|
2026-05-26 04:17:52 +08:00
|
|
|
|
import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtime.js';
|
2026-05-26 01:31:28 +08:00
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
(function(){
|
|
|
|
|
|
if (window.__mnoteSidebarTreeRuntimeStarted) return;
|
|
|
|
|
|
window.__mnoteSidebarTreeRuntimeStarted = true;
|
|
|
|
|
|
|
|
|
|
|
|
var FILETREE_DRAG_MIME = 'application/x-mnote-filetree-row-ids';
|
|
|
|
|
|
var MNOTE_SIDEBAR_TREE_MODE_KEY = 'mnote.sidebar.tree.mode';
|
|
|
|
|
|
var MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX = 'mnote.localFolder.recentRoots:';
|
|
|
|
|
|
var MNOTE_LAST_CLOUD_WORKSPACE_KEY = 'mnote.workspace.lastCloudWorkspaceId';
|
|
|
|
|
|
var MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY = 'mnote.global.showHeadingNumbers';
|
|
|
|
|
|
var mnoteNavigationInFlight = '';
|
|
|
|
|
|
var draggingFileTreeRowIds = [];
|
|
|
|
|
|
var activeFileTreeDropRow = null;
|
|
|
|
|
|
var sidebarFileTreeClipboard = null;
|
|
|
|
|
|
var sidebarFileTreeSelection = {
|
|
|
|
|
|
selectedRowIds: new Set(),
|
|
|
|
|
|
anchorRowId: null,
|
|
|
|
|
|
focusedRowId: null
|
|
|
|
|
|
};
|
|
|
|
|
|
var activeTreeContextMenu = null;
|
|
|
|
|
|
var pageUiState = {
|
|
|
|
|
|
pageOptions: null,
|
2026-05-28 22:01:44 +08:00
|
|
|
|
pageWidthPreferences: null,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
historySnapshots: [],
|
|
|
|
|
|
pageSettingsOpen: false,
|
|
|
|
|
|
localIndexSummary: {
|
|
|
|
|
|
scopeKey: '',
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
error: '',
|
|
|
|
|
|
backlinks: null,
|
|
|
|
|
|
tags: null
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function closestAction(target, selector) {
|
|
|
|
|
|
var node = target && target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
|
|
|
|
|
|
return node && typeof node.closest === 'function' ? node.closest(selector) : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 22:53:15 +08:00
|
|
|
|
function sidebarShellRuntimeFunction(name) {
|
|
|
|
|
|
var runtime = window.__mnoteSidebarShellRuntime;
|
|
|
|
|
|
return runtime && typeof runtime[name] === 'function' ? runtime[name] : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function toggleWorkspaceSidebar(trigger) {
|
2026-05-25 22:53:15 +08:00
|
|
|
|
var runtimeFn = sidebarShellRuntimeFunction('toggleWorkspaceSidebar');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(trigger);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function installWorkspaceSidebarResizer() {
|
2026-05-25 22:53:15 +08:00
|
|
|
|
var runtimeFn = sidebarShellRuntimeFunction('installWorkspaceSidebarResizer');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function escapeHtml(value) {
|
|
|
|
|
|
return String(value == null ? '' : value)
|
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function cssEscape(value) {
|
|
|
|
|
|
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(value);
|
|
|
|
|
|
return String(value).replace(/["\\]/g, '\\$&');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseJsonScript(id) {
|
|
|
|
|
|
var node = document.getElementById(id);
|
|
|
|
|
|
if (!node) return null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(node.textContent || 'null');
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentDocumentId() {
|
|
|
|
|
|
var match = window.location.pathname.match(/^\/documents\/([^\/]+)/);
|
|
|
|
|
|
if (!match) match = window.location.pathname.match(/^\/mindmap\/([^\/]+)\/([^\/]+)/);
|
|
|
|
|
|
if (match) return decodeURIComponent(match[1]);
|
|
|
|
|
|
var params = new URLSearchParams(window.location.search);
|
|
|
|
|
|
var fromQuery = (params.get('documentId') || params.get('pageId') || '').trim();
|
|
|
|
|
|
if (fromQuery) return fromQuery;
|
|
|
|
|
|
var activePane = document.querySelector('.document-pane[data-pane-role="primary"][data-pane-document-id], .document-pane[data-pane-visible="true"][data-pane-document-id]');
|
|
|
|
|
|
if (activePane instanceof HTMLElement) {
|
|
|
|
|
|
var paneDocumentId = (activePane.getAttribute('data-pane-document-id') || '').trim();
|
|
|
|
|
|
if (paneDocumentId) return paneDocumentId;
|
|
|
|
|
|
}
|
|
|
|
|
|
var shell = document.querySelector('.document-shell[data-document-id]');
|
|
|
|
|
|
if (shell instanceof HTMLElement) return (shell.getAttribute('data-document-id') || '').trim();
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function localFolderSelfChangeSuppressions() {
|
|
|
|
|
|
window.__mnoteLocalFolderSelfChangeSuppressions = window.__mnoteLocalFolderSelfChangeSuppressions || new Map();
|
|
|
|
|
|
return window.__mnoteLocalFolderSelfChangeSuppressions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function persistLocalFolderSelfChangeSuppression(documentId, expiresAt) {
|
|
|
|
|
|
var doc = String(documentId || '').trim();
|
|
|
|
|
|
if (!doc) return;
|
|
|
|
|
|
localFolderSelfChangeSuppressions().set(doc, expiresAt);
|
|
|
|
|
|
try {
|
|
|
|
|
|
var key = 'mnote.localFolder.selfChangeSuppressions.v1';
|
|
|
|
|
|
var existing = JSON.parse(window.sessionStorage.getItem(key) || '{}');
|
|
|
|
|
|
existing[doc] = expiresAt;
|
|
|
|
|
|
window.sessionStorage.setItem(key, JSON.stringify(existing));
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var EDITOR_UPLOAD_ROOT_SELECTOR = '[data-editor-host-kind="leptos_tiptap_island"], [data-editor-host-kind="leptos_tiptap_resource"]';
|
|
|
|
|
|
|
|
|
|
|
|
function editorUploadRootFromElement(target) {
|
|
|
|
|
|
if (!(target instanceof Element)) return null;
|
|
|
|
|
|
var root = target.closest(EDITOR_UPLOAD_ROOT_SELECTOR);
|
|
|
|
|
|
return root instanceof HTMLElement ? root : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function rememberEditorUploadRootFromTarget(target) {
|
|
|
|
|
|
var root = editorUploadRootFromElement(target);
|
|
|
|
|
|
if (root instanceof HTMLElement) {
|
|
|
|
|
|
window.__mnoteLastEditorUploadRoot = root;
|
|
|
|
|
|
return root;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('pointerdown', function(event) {
|
|
|
|
|
|
rememberEditorUploadRootFromTarget(event.target);
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('focusin', function(event) {
|
|
|
|
|
|
rememberEditorUploadRootFromTarget(event.target);
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
|
|
|
|
function currentFileTreeActiveRowId() {
|
|
|
|
|
|
var explicitRowId = new URL(window.location.href).searchParams.get('restoreFocusRowId') || '';
|
|
|
|
|
|
if (explicitRowId) return explicitRowId;
|
|
|
|
|
|
var mindmapMatch = window.location.pathname.match(/^\/mindmap\/([^\/]+)\/([^\/]+)/);
|
|
|
|
|
|
if (mindmapMatch) return 'asset:' + decodeURIComponent(mindmapMatch[2]);
|
|
|
|
|
|
var documentId = currentDocumentId();
|
|
|
|
|
|
return documentId ? 'doc:' + documentId : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentPageAggregate() {
|
|
|
|
|
|
return parseJsonScript('__MNOTE_PAGE_AGGREGATE__') || {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 01:31:28 +08:00
|
|
|
|
const sidebarWorkspace = createSidebarWorkspaceRuntime({
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
escapeHtml,
|
|
|
|
|
|
parseJsonScript,
|
|
|
|
|
|
setCommandPending: (...args) => setCommandPending(...args),
|
|
|
|
|
|
sidebarShellRuntimeFunction,
|
|
|
|
|
|
});
|
|
|
|
|
|
const normalizeSidebarTreeMode = (...args) => sidebarWorkspace.normalizeSidebarTreeMode(...args);
|
|
|
|
|
|
const readStoredSidebarTreeMode = (...args) => sidebarWorkspace.readStoredSidebarTreeMode(...args);
|
|
|
|
|
|
const persistSidebarTreeMode = (...args) => sidebarWorkspace.persistSidebarTreeMode(...args);
|
|
|
|
|
|
const activeSidebarTreeMode = (...args) => sidebarWorkspace.activeSidebarTreeMode(...args);
|
|
|
|
|
|
const resolveWorkspaceId = (...args) => sidebarWorkspace.resolveWorkspaceId(...args);
|
|
|
|
|
|
const currentWorkspaceId = (...args) => sidebarWorkspace.currentWorkspaceId(...args);
|
|
|
|
|
|
const currentSourceKind = (...args) => sidebarWorkspace.currentSourceKind(...args);
|
|
|
|
|
|
const currentRootUri = (...args) => sidebarWorkspace.currentRootUri(...args);
|
|
|
|
|
|
const copyWorkspaceSourceParams = (...args) => sidebarWorkspace.copyWorkspaceSourceParams(...args);
|
|
|
|
|
|
const currentWorkspaceSourcePayload = (...args) => sidebarWorkspace.currentWorkspaceSourcePayload(...args);
|
|
|
|
|
|
const openTrashModal = (...args) => sidebarWorkspace.openTrashModal(...args);
|
|
|
|
|
|
const closeTrashModal = (...args) => sidebarWorkspace.closeTrashModal(...args);
|
|
|
|
|
|
const closeLocalFolderDialog = (...args) => sidebarWorkspace.closeLocalFolderDialog(...args);
|
|
|
|
|
|
const requestOpenLocalFolder = (...args) => sidebarWorkspace.requestOpenLocalFolder(...args);
|
|
|
|
|
|
const createDefaultLocalWorkspace = (...args) => sidebarWorkspace.createDefaultLocalWorkspace(...args);
|
|
|
|
|
|
const closeWorkspaceSourceMenu = (...args) => sidebarWorkspace.closeWorkspaceSourceMenu(...args);
|
|
|
|
|
|
const openWorkspaceSourceMenu = (...args) => sidebarWorkspace.openWorkspaceSourceMenu(...args);
|
|
|
|
|
|
const closeAccountMenu = (...args) => sidebarWorkspace.closeAccountMenu(...args);
|
|
|
|
|
|
const openAccountMenu = (...args) => sidebarWorkspace.openAccountMenu(...args);
|
|
|
|
|
|
sidebarWorkspace.autoOpenRecentLocalRootOnHome();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const sidebarPageSettings = createSidebarPageSettingsRuntime({
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentPageAggregate,
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
currentWorkspaceSourcePayload,
|
|
|
|
|
|
escapeHtml,
|
|
|
|
|
|
globalShowHeadingNumbersKey: MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY,
|
|
|
|
|
|
pageUiState,
|
|
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
searchText,
|
|
|
|
|
|
});
|
|
|
|
|
|
const applyPageOptionsToShell = (...args) => sidebarPageSettings.applyPageOptionsToShell(...args);
|
|
|
|
|
|
const closePageHistoryDrawer = (...args) => sidebarPageSettings.closePageHistoryDrawer(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const closeAllSettingsPopovers = (...args) => sidebarPageSettings.closeAllSettingsPopovers(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const closePageSettingsPopover = (...args) => sidebarPageSettings.closePageSettingsPopover(...args);
|
|
|
|
|
|
const closePageShareDialog = (...args) => sidebarPageSettings.closePageShareDialog(...args);
|
|
|
|
|
|
const currentPageOptions = (...args) => sidebarPageSettings.currentPageOptions(...args);
|
|
|
|
|
|
const ensureHistorySnapshotsSeeded = (...args) => sidebarPageSettings.ensureHistorySnapshotsSeeded(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const isAnySettingsOpen = (...args) => sidebarPageSettings.isAnySettingsOpen(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const isPageSettingsOpen = (...args) => sidebarPageSettings.isPageSettingsOpen(...args);
|
|
|
|
|
|
const openPageHistoryDrawer = (...args) => sidebarPageSettings.openPageHistoryDrawer(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const openPageIndexSettingsPopover = (...args) => sidebarPageSettings.openPageIndexSettingsPopover(...args);
|
2026-06-07 01:10:31 +08:00
|
|
|
|
const openKnowledgeRagSettingsPopover = (...args) => sidebarPageSettings.openKnowledgeRagSettingsPopover(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const openPageShareDialog = (...args) => sidebarPageSettings.openPageShareDialog(...args);
|
|
|
|
|
|
const pageOptionIsSupported = (...args) => sidebarPageSettings.pageOptionIsSupported(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const addLocalIndexRange = (...args) => sidebarPageSettings.addLocalIndexRange(...args);
|
2026-06-07 01:10:31 +08:00
|
|
|
|
const addKnowledgeRagSourceInput = (...args) => sidebarPageSettings.addKnowledgeRagSourceInput(...args);
|
|
|
|
|
|
const deleteKnowledgeRagSource = (...args) => sidebarPageSettings.deleteKnowledgeRagSource(...args);
|
|
|
|
|
|
const ingestKnowledgeRagSources = (...args) => sidebarPageSettings.ingestKnowledgeRagSources(...args);
|
|
|
|
|
|
const ingestSingleKnowledgeRagSource = (...args) => sidebarPageSettings.ingestSingleKnowledgeRagSource(...args);
|
|
|
|
|
|
const loadKnowledgeRagStatus = (...args) => sidebarPageSettings.loadKnowledgeRagStatus(...args);
|
|
|
|
|
|
const openKnowledgeRagDashboard = (...args) => sidebarPageSettings.openKnowledgeRagDashboard(...args);
|
|
|
|
|
|
const pruneKnowledgeRagRegistry = (...args) => sidebarPageSettings.pruneKnowledgeRagRegistry(...args);
|
|
|
|
|
|
const setKnowledgeRagSourceFilter = (...args) => sidebarPageSettings.setKnowledgeRagSourceFilter(...args);
|
|
|
|
|
|
const useKnowledgeRagFileTreeSelection = (...args) => sidebarPageSettings.useKnowledgeRagFileTreeSelection(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const persistLocalIndexSettings = (...args) => sidebarPageSettings.persistLocalIndexSettings(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const persistPageOptionsPatch = (...args) => sidebarPageSettings.persistPageOptionsPatch(...args);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
const persistPageWidthPreference = (...args) => sidebarPageSettings.persistPageWidthPreference(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const refreshLocalIndex = (...args) => sidebarPageSettings.refreshLocalIndex(...args);
|
2026-06-07 01:10:31 +08:00
|
|
|
|
const removeKnowledgeRagSourceInput = (...args) => sidebarPageSettings.removeKnowledgeRagSourceInput(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const removeLocalIndexRange = (...args) => sidebarPageSettings.removeLocalIndexRange(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
const recordPageHistorySnapshot = (...args) => sidebarPageSettings.recordPageHistorySnapshot(...args);
|
|
|
|
|
|
const renderPageSettingsPopover = (...args) => sidebarPageSettings.renderPageSettingsPopover(...args);
|
|
|
|
|
|
const setActivePageSettingsTab = (...args) => sidebarPageSettings.setActivePageSettingsTab(...args);
|
|
|
|
|
|
const togglePageSettingsPopover = (...args) => sidebarPageSettings.togglePageSettingsPopover(...args);
|
|
|
|
|
|
const updatePageSettingsTriggerState = (...args) => sidebarPageSettings.updatePageSettingsTriggerState(...args);
|
|
|
|
|
|
const writeGlobalShowHeadingNumbers = (...args) => sidebarPageSettings.writeGlobalShowHeadingNumbers(...args);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
const loadPageWidthPreferences = (...args) => sidebarPageSettings.loadPageWidthPreferences(...args);
|
2026-05-26 04:17:52 +08:00
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function setCommandPending(trigger, pending) {
|
|
|
|
|
|
if (!(trigger instanceof HTMLElement)) return;
|
|
|
|
|
|
trigger.setAttribute('data-pending', pending ? 'true' : 'false');
|
|
|
|
|
|
if ('disabled' in trigger) {
|
|
|
|
|
|
if (pending) trigger.setAttribute('disabled', 'disabled');
|
|
|
|
|
|
else trigger.removeAttribute('disabled');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function dispatchTreeCommand(trigger, body) {
|
|
|
|
|
|
setCommandPending(trigger, true);
|
|
|
|
|
|
var commandBody = Object.assign({}, currentWorkspaceSourcePayload(), body || {});
|
|
|
|
|
|
try {
|
|
|
|
|
|
var response = await fetch('/api/tree/commands', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(commandBody)
|
|
|
|
|
|
});
|
|
|
|
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
|
|
|
|
if (!response.ok || !payload || !payload.result) {
|
|
|
|
|
|
throw new Error((payload && payload.message) || 'tree_command_failed_' + response.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent('tree:local-command', { detail: { body: commandBody, result: payload.result } }));
|
|
|
|
|
|
setCommandPending(trigger, false);
|
|
|
|
|
|
return payload.result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
setCommandPending(trigger, false);
|
|
|
|
|
|
if (trigger instanceof HTMLElement) {
|
|
|
|
|
|
trigger.setAttribute('data-error', error instanceof Error ? error.message : String(error));
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:35:08 +08:00
|
|
|
|
var sidebarPageTree = null;
|
|
|
|
|
|
const navigateToDocument = (...args) => sidebarPageTree.navigateToDocument(...args);
|
|
|
|
|
|
const createPage = (...args) => sidebarPageTree.createPage(...args);
|
|
|
|
|
|
const startPageDrag = (...args) => sidebarPageTree.startPageDrag(...args);
|
|
|
|
|
|
const readPageDragNodeId = (...args) => sidebarPageTree.readPageDragNodeId(...args);
|
|
|
|
|
|
const clearPageDropFeedback = (...args) => sidebarPageTree.clearPageDropFeedback(...args);
|
|
|
|
|
|
const setActivePageDropRow = (...args) => sidebarPageTree.setActivePageDropRow(...args);
|
|
|
|
|
|
const clearPageDragState = (...args) => sidebarPageTree.clearPageDragState(...args);
|
|
|
|
|
|
const canDropPage = (...args) => sidebarPageTree.canDropPage(...args);
|
|
|
|
|
|
const pageDropPosition = (...args) => sidebarPageTree.pageDropPosition(...args);
|
|
|
|
|
|
const resolvePageMoveTarget = (...args) => sidebarPageTree.resolvePageMoveTarget(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
async function createFileTreeFolder(trigger, parentId) {
|
|
|
|
|
|
if (currentSourceKind() !== 'local_folder') return false;
|
|
|
|
|
|
var workspaceId = resolveWorkspaceId(trigger || document.body);
|
|
|
|
|
|
var effectiveParentId = String(parentId || '').trim();
|
|
|
|
|
|
var result = await dispatchTreeCommand(trigger || document.body, {
|
|
|
|
|
|
action: 'create_folder',
|
|
|
|
|
|
workspaceId: workspaceId,
|
|
|
|
|
|
parentId: effectiveParentId || null,
|
2026-05-27 11:31:12 +08:00
|
|
|
|
title: '新建文件夹'
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
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) {
|
2026-05-25 22:53:15 +08:00
|
|
|
|
var runtimeFn = sidebarShellRuntimeFunction('applySidebarTreeTab');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(mode, shell);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function switchSidebarTreeTab(trigger) {
|
2026-05-25 22:53:15 +08:00
|
|
|
|
var runtimeFn = sidebarShellRuntimeFunction('switchSidebarTreeTab');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(trigger);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function restoreSidebarTreeTab() {
|
2026-05-25 22:53:15 +08:00
|
|
|
|
var runtimeFn = sidebarShellRuntimeFunction('restoreSidebarTreeTab');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 01:51:07 +08:00
|
|
|
|
const sidebarTreeLiveApply = createSidebarTreeLiveApplyRuntime({
|
|
|
|
|
|
activeSidebarTreeMode,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId: (...args) => currentDocumentId(...args),
|
|
|
|
|
|
currentFileTreeActiveRowId: (...args) => currentFileTreeActiveRowId(...args),
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
currentWorkspaceId,
|
|
|
|
|
|
escapeHtml,
|
|
|
|
|
|
fileTreeRuntimeFunction: (...args) => fileTreeRuntimeFunction(...args),
|
|
|
|
|
|
localFilePathFromAssetId: (...args) => localFilePathFromAssetId(...args),
|
|
|
|
|
|
navigateToDocument,
|
|
|
|
|
|
refreshEditorLocalAttachmentExistence: (...args) => refreshEditorLocalAttachmentExistence(...args),
|
|
|
|
|
|
restoreSidebarTreeTab,
|
|
|
|
|
|
rowTitle: (...args) => rowTitle(...args),
|
|
|
|
|
|
schedulePendingLocalFolderRestoreFocus: (...args) => schedulePendingLocalFolderRestoreFocus(...args),
|
|
|
|
|
|
shortMindmapFileName: (...args) => shortMindmapFileName(...args),
|
|
|
|
|
|
syncSidebarFileTreeSelection: (...args) => syncSidebarFileTreeSelection(...args),
|
|
|
|
|
|
});
|
|
|
|
|
|
const updateTitleEverywhere = (...args) => sidebarTreeLiveApply.updateTitleEverywhere(...args);
|
2026-05-26 07:08:26 +08:00
|
|
|
|
const commandDocumentId = (...args) => sidebarTreeLiveApply.commandDocumentId(...args);
|
2026-05-26 02:35:08 +08:00
|
|
|
|
sidebarPageTree = createSidebarPageTreeRuntime({
|
|
|
|
|
|
activeSidebarTreeMode,
|
|
|
|
|
|
commandDocumentId,
|
|
|
|
|
|
copyWorkspaceSourceParams,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
2026-05-27 11:31:12 +08:00
|
|
|
|
currentRootUri,
|
2026-05-26 02:35:08 +08:00
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
dispatchTreeCommand,
|
|
|
|
|
|
normalizeSidebarTreeMode,
|
|
|
|
|
|
persistLocalFolderSelfChangeSuppression,
|
|
|
|
|
|
persistSidebarTreeMode,
|
|
|
|
|
|
refreshLocalFolderSidebarSnapshot: (...args) => refreshLocalFolderSidebarSnapshot(...args),
|
2026-05-27 16:32:05 +08:00
|
|
|
|
refreshLocalFolderAfterCommand: (...args) => refreshLocalFolderAfterCommand(...args),
|
2026-05-26 02:35:08 +08:00
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
rowCenter: (...args) => rowCenter(...args),
|
|
|
|
|
|
selectSidebarFileTreeDocument: (...args) => selectSidebarFileTreeDocument(...args),
|
|
|
|
|
|
toggleChildren: (...args) => toggleChildren(...args),
|
|
|
|
|
|
dispatchSidebarEvent: (...args) => dispatchSidebarEvent(...args),
|
|
|
|
|
|
openPageTreeContextMenu: (...args) => openPageTreeContextMenu(...args),
|
|
|
|
|
|
updateTitleEverywhere: (...args) => updateTitleEverywhere(...args),
|
|
|
|
|
|
});
|
2026-05-26 01:51:07 +08:00
|
|
|
|
const fileTreePageTitle = (...args) => sidebarTreeLiveApply.fileTreePageTitle(...args);
|
|
|
|
|
|
const isFileTreePageRow = (...args) => sidebarTreeLiveApply.isFileTreePageRow(...args);
|
|
|
|
|
|
const normalizeFileTreePageRenameTitle = (...args) => sidebarTreeLiveApply.normalizeFileTreePageRenameTitle(...args);
|
|
|
|
|
|
const validateFileTreeRename = (...args) => sidebarTreeLiveApply.validateFileTreeRename(...args);
|
|
|
|
|
|
const removeDocumentRowForMode = (...args) => sidebarTreeLiveApply.removeDocumentRowForMode(...args);
|
|
|
|
|
|
const applyCreatedDocumentLocally = (...args) => sidebarTreeLiveApply.applyCreatedDocumentLocally(...args);
|
|
|
|
|
|
const localCommandNeedsProjectionRefresh = (...args) => sidebarTreeLiveApply.localCommandNeedsProjectionRefresh(...args);
|
2026-05-27 16:32:05 +08:00
|
|
|
|
const refreshLocalFolderAfterCommand = (...args) => sidebarTreeLiveApply.refreshLocalFolderAfterCommand(...args);
|
2026-05-26 01:51:07 +08:00
|
|
|
|
const applyMoveDocumentDelta = (...args) => sidebarTreeLiveApply.applyMoveDocumentDelta(...args);
|
|
|
|
|
|
const applyRemoveDocumentDelta = (...args) => sidebarTreeLiveApply.applyRemoveDocumentDelta(...args);
|
|
|
|
|
|
const setTreeLiveApplyError = (...args) => sidebarTreeLiveApply.setTreeLiveApplyError(...args);
|
|
|
|
|
|
const objectIdentityAttr = (...args) => sidebarTreeLiveApply.objectIdentityAttr(...args);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
const renderPageProjection = (...args) => sidebarTreeLiveApply.renderPageProjection(...args);
|
2026-05-26 01:51:07 +08:00
|
|
|
|
const renderSidebarSnapshot = (...args) => sidebarTreeLiveApply.renderSidebarSnapshot(...args);
|
|
|
|
|
|
const refreshLocalFolderSidebarSnapshot = (...args) => sidebarTreeLiveApply.refreshLocalFolderSidebarSnapshot(...args);
|
|
|
|
|
|
const startLocalFolderSidebarWatch = (...args) => sidebarTreeLiveApply.startLocalFolderSidebarWatch(...args);
|
|
|
|
|
|
const deltaNeedsProjectionRefresh = (...args) => sidebarTreeLiveApply.deltaNeedsProjectionRefresh(...args);
|
|
|
|
|
|
const toggleChildren = (...args) => sidebarTreeLiveApply.toggleChildren(...args);
|
2026-05-27 11:31:12 +08:00
|
|
|
|
const revealFileTreeResource = (...args) => sidebarTreeLiveApply.revealFileTreeResource(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
function dispatchSidebarEvent(name, detail) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-tree-action', name);
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent(name, { detail: detail }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
function sidebarShortcutWorkspaceId() {
|
|
|
|
|
|
return currentWorkspaceId()
|
|
|
|
|
|
|| (document.getElementById('sidebar-file-tree-root') && document.getElementById('sidebar-file-tree-root').getAttribute('data-workspace-id') || '')
|
|
|
|
|
|
|| (document.getElementById('sidebar-tree-root') && document.getElementById('sidebar-tree-root').getAttribute('data-workspace-id') || '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function sidebarShortcutSourceKind() {
|
|
|
|
|
|
return currentSourceKind() || 'workspace';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentFileTreeScope() {
|
|
|
|
|
|
var params = new URLSearchParams(window.location.search);
|
|
|
|
|
|
var fromUrl = String(params.get('fileTreeScope') || '').trim();
|
|
|
|
|
|
if (fromUrl) return fromUrl;
|
|
|
|
|
|
var root = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
return root instanceof HTMLElement ? String(root.getAttribute('data-mnote-filetree-scope') || '').trim() : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
|
function recordNavigationRecent(payload) {
|
|
|
|
|
|
var body = Object.assign({
|
|
|
|
|
|
sourceKind: currentSourceKind() || 'local_folder',
|
|
|
|
|
|
rootUri: currentRootUri() || '',
|
|
|
|
|
|
}, payload || {});
|
|
|
|
|
|
if (!body.kind || !body.rootUri || !body.title) return Promise.resolve(false);
|
|
|
|
|
|
return fetch('/api/navigation/recent', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
accept: 'application/json',
|
|
|
|
|
|
'content-type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
|
|
}).then(function(response) {
|
|
|
|
|
|
return response.ok;
|
|
|
|
|
|
}).catch(function(error) {
|
|
|
|
|
|
console.warn('[mnote navigation] record recent failed', error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openNavigationPageForFolder(rootUri, relativePath, workspaceId, title, options) {
|
|
|
|
|
|
rootUri = String(rootUri || '').trim();
|
|
|
|
|
|
relativePath = String(relativePath || '').trim().replace(/^\/+|\/+$/g, '');
|
|
|
|
|
|
workspaceId = String(workspaceId || '').trim();
|
|
|
|
|
|
title = String(title || relativePath || '本地文件夹').trim();
|
|
|
|
|
|
if (!rootUri) return false;
|
|
|
|
|
|
void recordNavigationRecent({
|
|
|
|
|
|
kind: 'folder',
|
|
|
|
|
|
sourceKind: 'local_folder',
|
|
|
|
|
|
rootUri: rootUri,
|
|
|
|
|
|
relativePath: relativePath,
|
|
|
|
|
|
title: title,
|
|
|
|
|
|
workspaceId: workspaceId
|
|
|
|
|
|
});
|
|
|
|
|
|
var targetUrl = new URL('/', window.location.origin);
|
|
|
|
|
|
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
targetUrl.searchParams.set('sourceKind', 'local_folder');
|
|
|
|
|
|
targetUrl.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
targetUrl.searchParams.set('treeView', 'filetree');
|
|
|
|
|
|
if (relativePath) targetUrl.searchParams.set('fileTreeScope', relativePath);
|
|
|
|
|
|
var url = targetUrl.pathname + targetUrl.search;
|
|
|
|
|
|
if (options && options.newTab) {
|
|
|
|
|
|
window.open(url, '_blank', 'noopener');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.location.assign(url);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
function currentTopbarTitle() {
|
|
|
|
|
|
var title = document.querySelector('[data-page-title-current="true"]');
|
|
|
|
|
|
return title && title.textContent ? title.textContent.trim() : '无标题';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeRowTitleForShortcut(row, fallback) {
|
|
|
|
|
|
var title = row && row.querySelector ? row.querySelector(':scope > .tree-link > .tree-link-title') : null;
|
|
|
|
|
|
return title && title.textContent ? title.textContent.trim() : (fallback || '文件夹');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 10:35:21 +08:00
|
|
|
|
function isRetiredOcrSidecarMarkdownPath(relativePath) {
|
2026-06-02 17:17:49 +08:00
|
|
|
|
var normalized = String(relativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
|
|
|
|
|
|
return /(^|\/)[^/]+\.ocr\/[^/]+\.ocr\.md$/i.test(normalized);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
function sidebarShortcutRows() {
|
|
|
|
|
|
return Array.from(document.querySelectorAll('.wolai-starred-section [data-mnote-shortcut-kind]'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function shortcutMatches(row, kind, targetId, relativePath, documentId) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return false;
|
|
|
|
|
|
if ((row.getAttribute('data-mnote-shortcut-kind') || '') !== kind) return false;
|
|
|
|
|
|
if (documentId && (row.getAttribute('data-mnote-shortcut-document-id') || row.getAttribute('data-document-id') || '') === documentId) return true;
|
|
|
|
|
|
if (relativePath && (row.getAttribute('data-mnote-shortcut-relative-path') || '') === relativePath) return true;
|
|
|
|
|
|
return Boolean(targetId && (row.getAttribute('data-mnote-shortcut-target-id') || row.getAttribute('data-node-id') || '') === targetId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function listSidebarShortcuts(workspaceId) {
|
|
|
|
|
|
var url = new URL('/api/sidebar/shortcuts', window.location.origin);
|
|
|
|
|
|
url.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
var response = await fetch(url.toString(), {
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: { accept: 'application/json' },
|
|
|
|
|
|
cache: 'no-store'
|
|
|
|
|
|
});
|
|
|
|
|
|
var payload = await response.json().catch(function() { return null; });
|
|
|
|
|
|
if (!response.ok) throw new Error(payload && (payload.error || payload.message) || 'sidebar_shortcuts_list_failed_' + response.status);
|
|
|
|
|
|
return payload && Array.isArray(payload.shortcuts) ? payload.shortcuts : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function shortcutRecordMatches(shortcut, kind, targetId, relativePath, documentId) {
|
|
|
|
|
|
if (!shortcut || shortcut.kind !== kind) return false;
|
|
|
|
|
|
var shortcutMetadata = shortcut && shortcut.metadata && typeof shortcut.metadata === 'object' ? shortcut.metadata : {};
|
|
|
|
|
|
var shortcutRootUri = String(shortcut.rootUri || shortcut.root_uri || shortcutMetadata.rootUri || shortcutMetadata.root_uri || '').trim();
|
|
|
|
|
|
var currentShortcutRootUri = currentRootUri();
|
|
|
|
|
|
if (shortcutRootUri && currentShortcutRootUri && shortcutRootUri !== currentShortcutRootUri) return false;
|
|
|
|
|
|
if (documentId && String(shortcut.documentId || shortcut.document_id || '') === documentId) return true;
|
|
|
|
|
|
if (relativePath && String(shortcut.relativePath || shortcut.relative_path || '') === relativePath) return true;
|
|
|
|
|
|
return Boolean(targetId && String(shortcut.targetId || shortcut.target_id || '') === targetId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function findSidebarShortcut(payload) {
|
|
|
|
|
|
var workspaceId = payload.workspaceId || sidebarShortcutWorkspaceId();
|
|
|
|
|
|
if (!workspaceId) return null;
|
|
|
|
|
|
var shortcuts = await listSidebarShortcuts(workspaceId);
|
|
|
|
|
|
return shortcuts.find(function(shortcut) {
|
|
|
|
|
|
return shortcutRecordMatches(shortcut, payload.kind, payload.targetId, payload.relativePath, payload.documentId);
|
|
|
|
|
|
}) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function upsertSidebarShortcut(payload) {
|
|
|
|
|
|
var response = await fetch('/api/sidebar/shortcuts', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: { accept: 'application/json', 'content-type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(payload)
|
|
|
|
|
|
});
|
|
|
|
|
|
var result = await response.json().catch(function() { return null; });
|
|
|
|
|
|
if (!response.ok) throw new Error(result && (result.error || result.message) || 'sidebar_shortcut_upsert_failed_' + response.status);
|
|
|
|
|
|
return result && result.shortcut ? result.shortcut : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteSidebarShortcut(shortcutId) {
|
|
|
|
|
|
if (!shortcutId) return false;
|
|
|
|
|
|
var response = await fetch('/api/sidebar/shortcuts/' + encodeURIComponent(shortcutId), {
|
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: { accept: 'application/json' }
|
|
|
|
|
|
});
|
|
|
|
|
|
var result = await response.json().catch(function() { return null; });
|
|
|
|
|
|
if (!response.ok) throw new Error(result && (result.error || result.message) || 'sidebar_shortcut_delete_failed_' + response.status);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSidebarShortcutMenu() {
|
|
|
|
|
|
var existing = document.querySelector('[data-testid="mnote-sidebar-shortcut-menu"]');
|
|
|
|
|
|
if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
|
|
|
|
|
|
document.querySelectorAll('[data-mnote-shortcut-action="menu"][aria-expanded="true"]').forEach(function(button) {
|
|
|
|
|
|
button.setAttribute('aria-expanded', 'false');
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function removeSidebarShortcutByRow(row) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return false;
|
|
|
|
|
|
var shortcutId = String(row.getAttribute('data-mnote-shortcut-id') || '').trim();
|
|
|
|
|
|
if (!shortcutId) return false;
|
|
|
|
|
|
row.setAttribute('data-mnote-shortcut-pending', 'true');
|
|
|
|
|
|
try {
|
|
|
|
|
|
await deleteSidebarShortcut(shortcutId);
|
|
|
|
|
|
row.remove();
|
|
|
|
|
|
closeSidebarShortcutMenu();
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-sidebar-shortcut-last-action', 'remove');
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (row.isConnected) row.removeAttribute('data-mnote-shortcut-pending');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openSidebarShortcutMenu(row, trigger) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return;
|
|
|
|
|
|
closeSidebarShortcutMenu();
|
|
|
|
|
|
if (trigger instanceof HTMLElement) trigger.setAttribute('aria-expanded', 'true');
|
|
|
|
|
|
var rect = trigger && trigger.getBoundingClientRect ? trigger.getBoundingClientRect() : row.getBoundingClientRect();
|
|
|
|
|
|
var menu = document.createElement('div');
|
|
|
|
|
|
menu.className = 'mnote-tree-context-menu';
|
|
|
|
|
|
menu.setAttribute('role', 'menu');
|
|
|
|
|
|
menu.setAttribute('data-testid', 'mnote-sidebar-shortcut-menu');
|
|
|
|
|
|
menu.innerHTML = '<button type="button" class="mnote-tree-context-menu__item" role="menuitem" data-mnote-sidebar-shortcut-menu-action="open"><span class="material-symbols-outlined mnote-tree-context-menu__icon" data-icon="login" aria-hidden="true"></span><span class="mnote-tree-context-menu__label">在右侧边栏打开</span></button><div class="mnote-tree-context-menu__separator" role="separator"></div><button type="button" class="mnote-tree-context-menu__item" role="menuitem" data-mnote-sidebar-shortcut-menu-action="copy-link"><span class="material-symbols-outlined mnote-tree-context-menu__icon" data-icon="link" aria-hidden="true"></span><span class="mnote-tree-context-menu__label">复制访问链接</span></button><button type="button" class="mnote-tree-context-menu__item mnote-tree-context-menu__item--danger" role="menuitem" data-mnote-sidebar-shortcut-menu-action="remove"><span class="material-symbols-outlined mnote-tree-context-menu__icon" data-icon="star_off" aria-hidden="true"></span><span class="mnote-tree-context-menu__label">取消星标</span></button>';
|
|
|
|
|
|
menu.__mnoteShortcutRow = row;
|
|
|
|
|
|
document.body.appendChild(menu);
|
|
|
|
|
|
var width = menu.offsetWidth || 220;
|
|
|
|
|
|
var left = Math.min(Math.max(8, rect.right - width), Math.max(8, window.innerWidth - width - 8));
|
|
|
|
|
|
var top = Math.min(Math.max(8, rect.bottom + 4), Math.max(8, window.innerHeight - (menu.offsetHeight || 120) - 8));
|
|
|
|
|
|
menu.style.left = left + 'px';
|
|
|
|
|
|
menu.style.top = top + 'px';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeSidebarShortcutRow(shortcut) {
|
|
|
|
|
|
sidebarShortcutRows().forEach(function(row) {
|
|
|
|
|
|
if (shortcutMatches(
|
|
|
|
|
|
row,
|
|
|
|
|
|
shortcut.kind,
|
|
|
|
|
|
String(shortcut.targetId || shortcut.target_id || ''),
|
|
|
|
|
|
String(shortcut.relativePath || shortcut.relative_path || ''),
|
|
|
|
|
|
String(shortcut.documentId || shortcut.document_id || '')
|
|
|
|
|
|
)) {
|
|
|
|
|
|
row.remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderSidebarShortcutRow(shortcut) {
|
|
|
|
|
|
if (!shortcut) return;
|
|
|
|
|
|
var section = document.querySelector('.wolai-starred-section');
|
|
|
|
|
|
if (!(section instanceof HTMLElement)) return;
|
|
|
|
|
|
removeSidebarShortcutRow(shortcut);
|
|
|
|
|
|
var kind = String(shortcut.kind || '').trim();
|
|
|
|
|
|
var shortcutId = String(shortcut.id || shortcut.targetId || shortcut.target_id || '').trim();
|
|
|
|
|
|
var targetId = String(shortcut.targetId || shortcut.target_id || '').trim();
|
|
|
|
|
|
var relativePath = String(shortcut.relativePath || shortcut.relative_path || '').trim();
|
|
|
|
|
|
var documentId = String(shortcut.documentId || shortcut.document_id || '').trim();
|
|
|
|
|
|
var sourceKind = String(shortcut.sourceKind || shortcut.source_kind || '').trim();
|
|
|
|
|
|
var metadata = shortcut.metadata && typeof shortcut.metadata === 'object' ? shortcut.metadata : {};
|
|
|
|
|
|
var rootUri = String(shortcut.rootUri || shortcut.root_uri || metadata.rootUri || metadata.root_uri || '').trim();
|
|
|
|
|
|
var workspaceId = String(shortcut.workspaceId || shortcut.workspace_id || sidebarShortcutWorkspaceId() || '').trim();
|
|
|
|
|
|
var title = String(shortcut.title || (kind === 'folder' ? '文件夹' : '无标题')).trim();
|
|
|
|
|
|
var href = '';
|
|
|
|
|
|
if (documentId) {
|
|
|
|
|
|
var targetUrl = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin);
|
|
|
|
|
|
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
if (sourceKind) targetUrl.searchParams.set('sourceKind', sourceKind);
|
|
|
|
|
|
if (rootUri) targetUrl.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
href = targetUrl.pathname + targetUrl.search;
|
|
|
|
|
|
}
|
|
|
|
|
|
var row = document.createElement(href ? 'a' : 'div');
|
|
|
|
|
|
row.className = 'wolai-page-row';
|
|
|
|
|
|
if (href) row.setAttribute('href', href);
|
|
|
|
|
|
else {
|
|
|
|
|
|
row.setAttribute('role', 'button');
|
|
|
|
|
|
row.setAttribute('tabindex', '0');
|
|
|
|
|
|
}
|
|
|
|
|
|
row.setAttribute('data-testid', 'wolai-sidebar-row');
|
|
|
|
|
|
row.setAttribute('data-node-id', shortcutId || targetId);
|
|
|
|
|
|
row.setAttribute('data-document-id', documentId || shortcutId || targetId);
|
|
|
|
|
|
if (shortcutId) row.setAttribute('data-mnote-shortcut-id', shortcutId);
|
|
|
|
|
|
if (workspaceId) row.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
row.setAttribute('data-mnote-shortcut-kind', kind);
|
|
|
|
|
|
if (sourceKind) row.setAttribute('data-mnote-shortcut-source-kind', sourceKind);
|
|
|
|
|
|
row.setAttribute('data-mnote-shortcut-target-id', targetId);
|
|
|
|
|
|
if (relativePath) row.setAttribute('data-mnote-shortcut-relative-path', relativePath);
|
|
|
|
|
|
if (rootUri) row.setAttribute('data-mnote-shortcut-root-uri', rootUri);
|
|
|
|
|
|
if (documentId) row.setAttribute('data-mnote-shortcut-document-id', documentId);
|
|
|
|
|
|
row.setAttribute('data-depth', '0');
|
|
|
|
|
|
row.setAttribute('data-active', String(documentId && documentId === currentDocumentId()));
|
|
|
|
|
|
row.innerHTML = '<span class="wolai-row-caret" aria-hidden="true">›</span><span class="wolai-row-icon"><span class="material-symbols-outlined wolai-row-symbol" data-icon="' + (kind === 'folder' ? 'folder_open' : 'home') + '" aria-hidden="true"></span></span><span class="wolai-row-title">' + escapeHtml(title) + '</span><button type="button" class="wolai-row-more" data-mnote-shortcut-action="menu" aria-label="更多操作" title="更多操作"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>';
|
|
|
|
|
|
section.appendChild(row);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toggleSidebarShortcut(payload) {
|
|
|
|
|
|
var workspaceId = payload.workspaceId || sidebarShortcutWorkspaceId();
|
|
|
|
|
|
if (!workspaceId) return false;
|
|
|
|
|
|
var normalized = Object.assign({}, payload, { workspaceId: workspaceId });
|
|
|
|
|
|
var existing = await findSidebarShortcut(normalized);
|
|
|
|
|
|
if (existing && existing.id) {
|
|
|
|
|
|
await deleteSidebarShortcut(existing.id);
|
|
|
|
|
|
removeSidebarShortcutRow(existing);
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-sidebar-shortcut-last-action', 'remove');
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
var shortcut = await upsertSidebarShortcut(normalized);
|
|
|
|
|
|
renderSidebarShortcutRow(shortcut);
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-sidebar-shortcut-last-action', 'upsert');
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentPageShortcutPayload() {
|
|
|
|
|
|
var documentId = currentDocumentId();
|
|
|
|
|
|
var workspaceId = sidebarShortcutWorkspaceId();
|
|
|
|
|
|
if (!documentId || !workspaceId) return null;
|
|
|
|
|
|
return {
|
|
|
|
|
|
workspaceId: workspaceId,
|
|
|
|
|
|
kind: 'page',
|
|
|
|
|
|
sourceKind: sidebarShortcutSourceKind(),
|
|
|
|
|
|
targetId: documentId,
|
|
|
|
|
|
documentId: documentId,
|
|
|
|
|
|
title: currentTopbarTitle(),
|
|
|
|
|
|
icon: 'star',
|
|
|
|
|
|
rootUri: currentRootUri(),
|
|
|
|
|
|
metadataJson: JSON.stringify({ rootUri: currentRootUri() })
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function folderShortcutPayload(detail, trigger) {
|
|
|
|
|
|
detail = detail || {};
|
|
|
|
|
|
var row = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
|
|
|
|
|
var rowKind = String(detail.rowKind || (row && row.getAttribute('data-row-kind')) || '').trim();
|
|
|
|
|
|
if (rowKind !== 'folder' && rowKind !== 'directory') return null;
|
|
|
|
|
|
var relativePath = String(detail.localRelativePath || (row && row.getAttribute('data-local-relative-path')) || '').trim();
|
|
|
|
|
|
if (!relativePath) return null;
|
|
|
|
|
|
var workspaceId = String(detail.workspaceId || sidebarShortcutWorkspaceId() || '').trim();
|
|
|
|
|
|
if (!workspaceId) return null;
|
|
|
|
|
|
var rowId = String(detail.rowId || (row && row.getAttribute('data-row-id')) || '').trim();
|
|
|
|
|
|
return {
|
|
|
|
|
|
workspaceId: workspaceId,
|
|
|
|
|
|
kind: 'folder',
|
|
|
|
|
|
sourceKind: 'local_folder',
|
|
|
|
|
|
targetId: rowId || ('folder:' + relativePath),
|
|
|
|
|
|
relativePath: relativePath,
|
|
|
|
|
|
title: detail.title || fileTreeRowTitleForShortcut(row, relativePath),
|
|
|
|
|
|
icon: 'folder_open',
|
|
|
|
|
|
rootUri: currentRootUri(),
|
|
|
|
|
|
metadataJson: JSON.stringify({ rootUri: currentRootUri() })
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toggleCurrentPageSidebarShortcut(trigger) {
|
|
|
|
|
|
var payload = currentPageShortcutPayload();
|
|
|
|
|
|
if (!payload) return;
|
|
|
|
|
|
if (trigger instanceof HTMLElement) trigger.setAttribute('data-mnote-shortcut-pending', 'true');
|
|
|
|
|
|
try {
|
|
|
|
|
|
await toggleSidebarShortcut(payload);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (trigger instanceof HTMLElement) trigger.removeAttribute('data-mnote-shortcut-pending');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toggleFolderSidebarShortcut(detail, trigger) {
|
|
|
|
|
|
var payload = folderShortcutPayload(detail, trigger);
|
|
|
|
|
|
if (!payload) return false;
|
|
|
|
|
|
await toggleSidebarShortcut(payload);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensureStarredFolderFileTreeHost(workspaceId) {
|
|
|
|
|
|
var root = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (root instanceof HTMLElement) return root;
|
|
|
|
|
|
var panel = document.getElementById('wolai-sidebar-file-tree-panel');
|
|
|
|
|
|
if (!(panel instanceof HTMLElement)) return null;
|
|
|
|
|
|
var section = document.createElement('div');
|
|
|
|
|
|
section.className = 'sidebar-tree-section sidebar-file-tree-section';
|
|
|
|
|
|
root = document.createElement('div');
|
|
|
|
|
|
root.id = 'sidebar-file-tree-root';
|
|
|
|
|
|
root.className = 'sidebar-tree';
|
|
|
|
|
|
root.setAttribute('data-tree-shell-mode', 'filetree');
|
|
|
|
|
|
if (workspaceId) root.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
section.appendChild(root);
|
|
|
|
|
|
panel.appendChild(section);
|
|
|
|
|
|
return root;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 13:50:31 +08:00
|
|
|
|
function ensureStarredFolderPageTreeHost(workspaceId) {
|
|
|
|
|
|
var root = document.getElementById('sidebar-tree-root');
|
|
|
|
|
|
if (root instanceof HTMLElement) return root;
|
|
|
|
|
|
var panel = document.getElementById('wolai-sidebar-page-tree-panel') || document.querySelector('[data-mnote-sidebar-tree-panel="page"]');
|
|
|
|
|
|
if (!(panel instanceof HTMLElement)) return null;
|
|
|
|
|
|
var section = document.createElement('div');
|
|
|
|
|
|
section.className = 'sidebar-tree-section';
|
|
|
|
|
|
root = document.createElement('div');
|
|
|
|
|
|
root.id = 'sidebar-tree-root';
|
|
|
|
|
|
root.className = 'sidebar-tree';
|
|
|
|
|
|
root.setAttribute('data-tree-shell-mode', 'page');
|
|
|
|
|
|
if (workspaceId) root.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
section.appendChild(root);
|
|
|
|
|
|
panel.appendChild(section);
|
|
|
|
|
|
return root;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
function persistStarredFolderScope(workspaceId, rootUri, relativePath) {
|
|
|
|
|
|
var targetUrl = new URL(window.location.href);
|
|
|
|
|
|
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
targetUrl.searchParams.set('sourceKind', 'local_folder');
|
|
|
|
|
|
targetUrl.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
targetUrl.searchParams.set('treeView', 'filetree');
|
|
|
|
|
|
targetUrl.searchParams.set('fileTreeScope', relativePath);
|
|
|
|
|
|
window.history.replaceState(window.history.state, '', targetUrl.pathname + targetUrl.search + targetUrl.hash);
|
|
|
|
|
|
if (document.body instanceof HTMLElement) {
|
|
|
|
|
|
document.body.setAttribute('data-mnote-source-kind', 'local_folder');
|
|
|
|
|
|
document.body.setAttribute('data-mnote-root-uri', rootUri);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function readShortcutRootUri(row) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return '';
|
|
|
|
|
|
return String(row.getAttribute('data-mnote-shortcut-root-uri') || '').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function openStarredFolderShortcut(row) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return false;
|
|
|
|
|
|
var relativePath = String(row.getAttribute('data-mnote-shortcut-relative-path') || '').trim();
|
|
|
|
|
|
var workspaceId = String(row.getAttribute('data-workspace-id') || '').trim() || sidebarShortcutWorkspaceId();
|
|
|
|
|
|
var rootUri = readShortcutRootUri(row);
|
|
|
|
|
|
if (!relativePath || !rootUri || !workspaceId) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-sidebar-shortcut-open-error', !rootUri ? 'missing_root_uri' : 'missing_target');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
document.documentElement.removeAttribute('data-mnote-sidebar-shortcut-open-error');
|
|
|
|
|
|
var tab = document.querySelector('[data-mnote-sidebar-tree-tab="filetree"]');
|
|
|
|
|
|
if (tab instanceof HTMLElement) switchSidebarTreeTab(tab);
|
|
|
|
|
|
var root = ensureStarredFolderFileTreeHost(workspaceId);
|
2026-05-27 13:50:31 +08:00
|
|
|
|
var pageRoot = ensureStarredFolderPageTreeHost(workspaceId);
|
2026-05-27 11:31:12 +08:00
|
|
|
|
if (root instanceof HTMLElement) {
|
|
|
|
|
|
root.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope', relativePath);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope-title', row.textContent ? row.textContent.trim() : relativePath);
|
|
|
|
|
|
}
|
2026-05-27 13:50:31 +08:00
|
|
|
|
if (pageRoot instanceof HTMLElement) {
|
|
|
|
|
|
pageRoot.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
pageRoot.setAttribute('data-mnote-page-tree-scope', relativePath);
|
|
|
|
|
|
}
|
2026-05-27 11:31:12 +08:00
|
|
|
|
persistStarredFolderScope(workspaceId, rootUri, relativePath);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
void recordNavigationRecent({
|
|
|
|
|
|
kind: 'folder',
|
|
|
|
|
|
sourceKind: 'local_folder',
|
|
|
|
|
|
rootUri: rootUri,
|
|
|
|
|
|
relativePath: relativePath,
|
|
|
|
|
|
title: row.textContent ? row.textContent.trim() : relativePath,
|
|
|
|
|
|
workspaceId: workspaceId
|
|
|
|
|
|
});
|
2026-05-27 11:31:12 +08:00
|
|
|
|
document.documentElement.setAttribute('data-mnote-filetree-scope', relativePath);
|
|
|
|
|
|
var sidebarUrl = new URL('/api/tree/projections/sidebar', window.location.origin);
|
|
|
|
|
|
sidebarUrl.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
sidebarUrl.searchParams.set('sourceKind', 'local_folder');
|
|
|
|
|
|
sidebarUrl.searchParams.set('rootUri', rootUri);
|
2026-05-27 13:50:31 +08:00
|
|
|
|
sidebarUrl.searchParams.set('parentRelativePath', relativePath);
|
2026-05-27 11:31:12 +08:00
|
|
|
|
var url = new URL('/api/tree/projections/file', window.location.origin);
|
|
|
|
|
|
url.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
url.searchParams.set('sourceKind', 'local_folder');
|
|
|
|
|
|
url.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
url.searchParams.set('parentRelativePath', relativePath);
|
2026-05-27 12:53:55 +08:00
|
|
|
|
var sidebarRequest = fetch(sidebarUrl.toString(), {
|
2026-05-27 11:31:12 +08:00
|
|
|
|
method: 'GET',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: { accept: 'application/json' },
|
|
|
|
|
|
cache: 'no-store'
|
|
|
|
|
|
});
|
2026-05-27 12:53:55 +08:00
|
|
|
|
var fileRequest = fetch(url.toString(), {
|
2026-05-27 11:31:12 +08:00
|
|
|
|
method: 'GET',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
headers: { accept: 'application/json' },
|
|
|
|
|
|
cache: 'no-store'
|
|
|
|
|
|
});
|
2026-05-27 12:53:55 +08:00
|
|
|
|
var response = await fileRequest;
|
2026-05-27 11:31:12 +08:00
|
|
|
|
var payload = await response.json().catch(function() { return null; });
|
|
|
|
|
|
if (!response.ok) throw new Error(payload && (payload.error || payload.message) || 'scoped_filetree_failed_' + response.status);
|
|
|
|
|
|
var fileProjection = payload && (payload.result || payload);
|
2026-05-27 12:53:55 +08:00
|
|
|
|
var renderedFile = renderSidebarSnapshot({
|
|
|
|
|
|
dataset: { kernel_file_tree_projection: fileProjection }
|
|
|
|
|
|
});
|
|
|
|
|
|
root = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (root instanceof HTMLElement) {
|
|
|
|
|
|
root.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope', relativePath);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope-title', row.textContent ? row.textContent.trim() : relativePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
var sidebarResponse = await sidebarRequest;
|
|
|
|
|
|
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) || {};
|
2026-06-04 18:51:16 +08:00
|
|
|
|
var renderedPage = sidebarProjection ? await renderPageProjection(sidebarProjection) : false;
|
2026-05-27 11:31:12 +08:00
|
|
|
|
root = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (root instanceof HTMLElement) {
|
|
|
|
|
|
root.setAttribute('data-workspace-id', workspaceId);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope', relativePath);
|
|
|
|
|
|
root.setAttribute('data-mnote-filetree-scope-title', row.textContent ? row.textContent.trim() : relativePath);
|
|
|
|
|
|
}
|
2026-05-27 12:53:55 +08:00
|
|
|
|
return renderedFile || renderedPage;
|
2026-05-27 11:31:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 01:58:00 +08:00
|
|
|
|
const sidebarFileTreeOpen = createSidebarFileTreeOpenRuntime({
|
|
|
|
|
|
copyWorkspaceSourceParams,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentRootUri,
|
2026-05-28 22:01:44 +08:00
|
|
|
|
currentSourceKind,
|
2026-05-26 01:58:00 +08:00
|
|
|
|
fileTreeIconKindForFileName: (...args) => fileTreeIconKindForFileName(...args),
|
|
|
|
|
|
getNavigationInFlight: () => mnoteNavigationInFlight,
|
|
|
|
|
|
isCodeAttachmentFileName: (...args) => isCodeAttachmentFileName(...args),
|
|
|
|
|
|
isNonOfficeAttachmentName: (...args) => isNonOfficeAttachmentName(...args),
|
2026-05-28 22:01:44 +08:00
|
|
|
|
isPdfAttachmentFileName: (...args) => isPdfAttachmentFileName(...args),
|
2026-05-26 01:58:00 +08:00
|
|
|
|
openCodeEditorAttachment: (...args) => openCodeEditorAttachment(...args),
|
|
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
setNavigationInFlight: (value) => { mnoteNavigationInFlight = String(value || ''); },
|
|
|
|
|
|
shouldOpenLocalResourceInNewWindow: (...args) => shouldOpenLocalResourceInNewWindow(...args),
|
|
|
|
|
|
uploadedFileSize: (...args) => uploadedFileSize(...args),
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
2026-05-26 01:58:00 +08:00
|
|
|
|
const inferOnlyOfficeFileType = (...args) => sidebarFileTreeOpen.inferOnlyOfficeFileType(...args);
|
|
|
|
|
|
const buildOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenUrl(...args);
|
|
|
|
|
|
const buildOnlyOfficeOpenPath = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenPath(...args);
|
2026-06-05 23:00:53 +08:00
|
|
|
|
const buildOfficePreviewOpenUrl = (...args) => sidebarFileTreeOpen.buildOfficePreviewOpenUrl(...args);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
const buildPdfPreviewOpenUrl = (...args) => sidebarFileTreeOpen.buildPdfPreviewOpenUrl(...args);
|
2026-05-26 01:58:00 +08:00
|
|
|
|
const buildLocalOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildLocalOnlyOfficeOpenUrl(...args);
|
|
|
|
|
|
const openLocalOfficeFileInActiveTab = (...args) => sidebarFileTreeOpen.openLocalOfficeFileInActiveTab(...args);
|
|
|
|
|
|
const buildMindmapOpenPath = (...args) => sidebarFileTreeOpen.buildMindmapOpenPath(...args);
|
|
|
|
|
|
const navigateToMindmapObject = (...args) => sidebarFileTreeOpen.navigateToMindmapObject(...args);
|
|
|
|
|
|
const isMindmapAssetDetail = (...args) => sidebarFileTreeOpen.isMindmapAssetDetail(...args);
|
|
|
|
|
|
const localFilePathFromAssetId = (...args) => sidebarFileTreeOpen.localFilePathFromAssetId(...args);
|
|
|
|
|
|
const buildLocalFileOpenUrl = (...args) => sidebarFileTreeOpen.buildLocalFileOpenUrl(...args);
|
|
|
|
|
|
const openLocalResourceInActiveTab = (...args) => sidebarFileTreeOpen.openLocalResourceInActiveTab(...args);
|
|
|
|
|
|
const readFileTreeObjectIdentity = (...args) => sidebarFileTreeOpen.readFileTreeObjectIdentity(...args);
|
|
|
|
|
|
const fetchCurrentOnlyOfficeUserId = (...args) => sidebarFileTreeOpen.fetchCurrentOnlyOfficeUserId(...args);
|
|
|
|
|
|
const openConvexAssetFromFileTree = (...args) => sidebarFileTreeOpen.openConvexAssetFromFileTree(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
2026-05-26 02:27:08 +08:00
|
|
|
|
var sidebarFileTreeUpload = null;
|
|
|
|
|
|
const fileTreeRowsForUploadPreflight = (...args) => sidebarFileTreeUpload.fileTreeRowsForUploadPreflight(...args);
|
|
|
|
|
|
const fileTreeRowCapabilities = (...args) => sidebarFileTreeUpload.fileTreeRowCapabilities(...args);
|
|
|
|
|
|
const fileTreeRowIsReadonly = (...args) => sidebarFileTreeUpload.fileTreeRowIsReadonly(...args);
|
|
|
|
|
|
const blockReadonlyFileTreeAction = (...args) => sidebarFileTreeUpload.blockReadonlyFileTreeAction(...args);
|
|
|
|
|
|
const fileTreeDocumentParentsForPreflight = (...args) => sidebarFileTreeUpload.fileTreeDocumentParentsForPreflight(...args);
|
|
|
|
|
|
const fileTreeTargetChildrenForPreflight = (...args) => sidebarFileTreeUpload.fileTreeTargetChildrenForPreflight(...args);
|
|
|
|
|
|
const fileTreeDropPreflightRows = (...args) => sidebarFileTreeUpload.fileTreeDropPreflightRows(...args);
|
|
|
|
|
|
const ensureFileTreeWritableTarget = (...args) => sidebarFileTreeUpload.ensureFileTreeWritableTarget(...args);
|
|
|
|
|
|
const fileTreeDocumentWorkspacesForUploadPreflight = (...args) => sidebarFileTreeUpload.fileTreeDocumentWorkspacesForUploadPreflight(...args);
|
|
|
|
|
|
const preflightFileTreeUploadTarget = (...args) => sidebarFileTreeUpload.preflightFileTreeUploadTarget(...args);
|
|
|
|
|
|
const fallbackFileTreeUploadTarget = (...args) => sidebarFileTreeUpload.fallbackFileTreeUploadTarget(...args);
|
|
|
|
|
|
const resolveFileTreeUploadTarget = (...args) => sidebarFileTreeUpload.resolveFileTreeUploadTarget(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
function localUploadRuntimeFunction(name) {
|
|
|
|
|
|
var runtime = window.__mnoteLocalUploadRuntime;
|
|
|
|
|
|
var fn = runtime && runtime[name];
|
|
|
|
|
|
return typeof fn === 'function' ? fn : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeRuntimeFunction(name) {
|
|
|
|
|
|
var runtime = window.__mnoteFileTreeRuntime;
|
|
|
|
|
|
var fn = runtime && runtime[name];
|
|
|
|
|
|
return typeof fn === 'function' ? fn : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeRuntimeDeps() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
currentSourceKind: currentSourceKind,
|
2026-06-01 09:29:12 +08:00
|
|
|
|
currentRootUri: currentRootUri,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
currentDocumentId: currentDocumentId,
|
|
|
|
|
|
localFilePathFromAssetId: localFilePathFromAssetId,
|
|
|
|
|
|
rowTitle: rowTitle,
|
|
|
|
|
|
resolveWorkspaceId: resolveWorkspaceId,
|
|
|
|
|
|
cssEscape: cssEscape,
|
|
|
|
|
|
escapeHtml: escapeHtml,
|
|
|
|
|
|
objectIdentityAttr: objectIdentityAttr,
|
|
|
|
|
|
uploadedAssetTitle: uploadedAssetTitle,
|
|
|
|
|
|
uploadedAssetType: uploadedAssetType,
|
|
|
|
|
|
fileTreeIconKindForFileName: fileTreeIconKindForFileName
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeSelectionRuntimeFunction(name) {
|
|
|
|
|
|
var runtime = window.__mnoteFileTreeSelectionRuntime;
|
|
|
|
|
|
var fn = runtime && runtime[name];
|
|
|
|
|
|
return typeof fn === 'function' ? fn : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeSelectionRuntimeDeps() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
cssEscape: cssEscape
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedAssetTitle(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAssetTitle');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
return String(asset && (asset.file_name || asset.title || asset.name) || '未命名附件').trim() || '未命名附件';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedAssetUrl(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAssetUrl');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 11:13:05 +08:00
|
|
|
|
function uploadedAssetMarkdownHref(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAssetMarkdownHref');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
var href = String(asset && (asset.markdownHref || asset.markdown_href) || '').trim();
|
|
|
|
|
|
if (href) {
|
|
|
|
|
|
var normalizedHref = href.replace(/\\/g, '/');
|
|
|
|
|
|
if (normalizedHref.indexOf('../') === 0 || normalizedHref.indexOf('/../') >= 0) return '';
|
|
|
|
|
|
return href;
|
|
|
|
|
|
}
|
|
|
|
|
|
var relativePath = String(asset && (asset.markdownRelativePath || asset.markdown_relative_path) || '').trim().replace(/\\/g, '/');
|
|
|
|
|
|
if (!relativePath) return '';
|
|
|
|
|
|
if (relativePath.indexOf('../') === 0 || relativePath.indexOf('/../') >= 0) return '';
|
|
|
|
|
|
if (relativePath.indexOf('./') === 0) return relativePath;
|
|
|
|
|
|
return './' + relativePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function localAssetOpenUrl(asset, download) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('localAssetOpenUrl');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset, download, { rootUri: currentRootUri() });
|
|
|
|
|
|
if (!isLocalUploadedAsset(asset)) return '';
|
|
|
|
|
|
var rootUri = String(asset && (asset.rootUri || asset.root_uri) || '').trim() || currentRootUri();
|
|
|
|
|
|
var rootRelativePath = String(asset && (asset.rootRelativePath || asset.root_relative_path) || '').trim();
|
|
|
|
|
|
if (!rootUri || !rootRelativePath) return '';
|
|
|
|
|
|
var url = new URL('/api/local-folder/files/open', window.location.origin);
|
|
|
|
|
|
url.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
url.searchParams.set('path', rootRelativePath);
|
|
|
|
|
|
if (download) url.searchParams.set('download', 'true');
|
|
|
|
|
|
return url.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedAssetType(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAssetType');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
return String(asset && (asset.asset_type || asset.assetType || asset.mime_type || '') || '').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fileTreeIconKindForFileName(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('fileTreeIconKindForFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
var name = String(fileName || '').trim().toLowerCase();
|
|
|
|
|
|
var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : '';
|
|
|
|
|
|
if (['doc', 'docx', 'odt', 'rtf', 'ppt', 'pptx', 'odp', 'xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return 'office';
|
|
|
|
|
|
if (ext === 'md' || ext === 'markdown') return 'markdown';
|
|
|
|
|
|
if (ext === 'pdf') return 'pdf';
|
|
|
|
|
|
if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp'].indexOf(ext) >= 0) return 'image';
|
2026-05-28 22:01:44 +08:00
|
|
|
|
if (['html', 'htm', 'css', 'scss', 'less', 'js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'vue', 'svelte', 'astro'].indexOf(ext) >= 0) return 'web';
|
|
|
|
|
|
if (['json', 'jsonc', 'json5', 'toml', 'yaml', 'yml', 'ini', 'env', 'xml', 'lock', 'hcl', 'tf', 'tfvars', 'nix', 'properties', 'conf', 'cfg', 'config', 'service', 'desktop', 'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'prettierrc', 'eslintrc'].indexOf(ext) >= 0 || ['.dockerignore', '.editorconfig', '.env', '.eslintrc', '.gitattributes', '.gitignore', '.npmrc', '.prettierrc', 'dockerfile', 'containerfile', 'makefile', 'cmakelists.txt', 'gemfile', 'rakefile', 'procfile'].indexOf(name) >= 0) return 'config';
|
|
|
|
|
|
if (['rs', 'py', 'go', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'rb', 'sh', 'bash', 'zsh', 'sql', 'lua', 'dart', 'kt', 'kts', 'swift', 'scala', 'gradle', 'groovy', 'clj', 'ex', 'exs', 'erl', 'hrl', 'fs', 'fsx', 'r', 'jl', 'm', 'mm', 'pl', 'pm', 'ps1', 'bat', 'cmd', 'psm1', 'psd1', 'proto', 'graphql', 'gql', 'prisma', 'cmake', 'bazel', 'bzl'].indexOf(ext) >= 0) return 'code';
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function shouldOpenLocalResourceInNewWindow(fileName) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isLocalUploadedAsset(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('isLocalUploadedAsset');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
var id = String(asset && asset.id || '').trim();
|
|
|
|
|
|
return String(asset && asset.sourceKind || '').trim() === 'local_folder' || id.indexOf('local:asset:') === 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedAssetExtension(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAssetExtension');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
var match = uploadedAssetTitle(asset).toLowerCase().match(/\.([a-z0-9]+)$/);
|
|
|
|
|
|
return match ? match[1] : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isNonOfficeAttachmentName(name, ext) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('isNonOfficeAttachmentName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(name, ext);
|
|
|
|
|
|
var codeFileNames = [
|
|
|
|
|
|
'.dockerignore', '.editorconfig', '.env', '.eslintrc', '.gitattributes', '.gitignore', '.npmrc', '.prettierrc',
|
|
|
|
|
|
'dockerfile', 'makefile', 'cmakelists.txt', 'gemfile', 'rakefile', 'procfile'
|
|
|
|
|
|
];
|
|
|
|
|
|
return [
|
|
|
|
|
|
'pdf', 'toml', 'json', 'yaml', 'yml', 'md', 'markdown', 'txt', 'ini', 'env', 'xml', 'html', 'htm', 'css', 'scss',
|
|
|
|
|
|
'less', 'js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'py', 'rs', 'go', 'java', 'c', 'cpp', 'h', 'hpp', 'cs',
|
|
|
|
|
|
'php', 'rb', 'sh', 'bash', 'zsh', 'sql', 'lock', 'log', 'vue', 'svelte', 'astro', 'jsonc', 'json5',
|
|
|
|
|
|
'mts', 'cts', 'lua', 'dart', 'kt', 'kts', 'swift', 'scala', 'gradle', 'groovy', 'clj',
|
|
|
|
|
|
'ex', 'exs', 'erl', 'hrl', 'fs', 'fsx', 'r', 'jl', 'm', 'mm', 'pl', 'pm', 'ps1', 'bat', 'cmd',
|
|
|
|
|
|
'psm1', 'psd1', 'dockerfile', 'containerfile', 'proto', 'graphql', 'gql', 'prisma', 'tf', 'tfvars',
|
|
|
|
|
|
'hcl', 'nix', 'cmake', 'bazel', 'bzl', 'properties', 'conf', 'cfg', 'config', 'service', 'desktop',
|
|
|
|
|
|
'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'prettierrc', 'eslintrc'
|
|
|
|
|
|
].indexOf(ext) >= 0 || codeFileNames.indexOf(name) >= 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function attachmentExtensionFromFileName(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('attachmentExtensionFromFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
var name = String(fileName || '').trim().toLowerCase();
|
|
|
|
|
|
return name.indexOf('.') >= 0 ? name.split('.').pop() : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isPdfAttachmentFileName(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('isPdfAttachmentFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
return attachmentExtensionFromFileName(fileName) === 'pdf';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isCodeAttachmentFileName(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('isCodeAttachmentFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
var name = String(fileName || '').trim().toLowerCase();
|
|
|
|
|
|
var ext = attachmentExtensionFromFileName(name);
|
|
|
|
|
|
return ext !== 'pdf' && isNonOfficeAttachmentName(name, ext);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function inferCodeAttachmentLanguage(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('inferCodeAttachmentLanguage');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
var name = String(fileName || '').trim().toLowerCase();
|
|
|
|
|
|
var ext = attachmentExtensionFromFileName(name);
|
|
|
|
|
|
var byName = {
|
|
|
|
|
|
'dockerfile': 'dockerfile',
|
|
|
|
|
|
'makefile': 'makefile',
|
|
|
|
|
|
'cmakelists.txt': 'cmake',
|
|
|
|
|
|
'.gitignore': 'gitignore',
|
|
|
|
|
|
'.gitattributes': 'gitattributes',
|
|
|
|
|
|
'.editorconfig': 'ini',
|
|
|
|
|
|
'.env': 'dotenv'
|
|
|
|
|
|
};
|
|
|
|
|
|
if (byName[name]) return byName[name];
|
|
|
|
|
|
var byExt = {
|
|
|
|
|
|
bash: 'bash', bat: 'batch', c: 'c', cjs: 'javascript', cmd: 'batch', conf: 'text', cpp: 'cpp',
|
|
|
|
|
|
cs: 'csharp', css: 'css', cts: 'typescript', dart: 'dart', dockerfile: 'dockerfile', env: 'dotenv',
|
|
|
|
|
|
go: 'go', gql: 'graphql', gradle: 'groovy', graphql: 'graphql', h: 'c', hcl: 'hcl', hpp: 'cpp',
|
|
|
|
|
|
htm: 'html', html: 'html', ini: 'ini', java: 'java', js: 'javascript', json: 'json', json5: 'json',
|
|
|
|
|
|
jsonc: 'jsonc', jsx: 'javascript', kt: 'kotlin', kts: 'kotlin', less: 'less', log: 'text',
|
|
|
|
|
|
lua: 'lua', m: 'objective-c', markdown: 'markdown', md: 'markdown', mjs: 'javascript',
|
|
|
|
|
|
mts: 'typescript', nix: 'nix', php: 'php', pl: 'perl', pm: 'perl', prisma: 'prisma',
|
|
|
|
|
|
proto: 'protobuf', ps1: 'powershell', py: 'python', r: 'r', rb: 'ruby', rs: 'rust',
|
|
|
|
|
|
scss: 'scss', sh: 'bash', sql: 'sql', svelte: 'svelte', swift: 'swift', tf: 'terraform',
|
|
|
|
|
|
tfvars: 'terraform', toml: 'toml', ts: 'typescript', tsx: 'typescript', txt: 'text',
|
|
|
|
|
|
vue: 'vue', xml: 'xml', yaml: 'yaml', yml: 'yaml', zsh: 'bash'
|
|
|
|
|
|
};
|
|
|
|
|
|
return byExt[ext] || 'text';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function attachmentClassForFileName(fileName) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('attachmentClassForFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(fileName);
|
|
|
|
|
|
var name = String(fileName || '').trim().toLowerCase();
|
|
|
|
|
|
var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : '';
|
|
|
|
|
|
if (['doc', 'docx', 'odt', 'rtf'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-word';
|
|
|
|
|
|
if (['ppt', 'pptx', 'odp'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-ppt';
|
|
|
|
|
|
if (['xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-sheet';
|
|
|
|
|
|
if (ext === 'pdf') return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-pdf';
|
|
|
|
|
|
if (isNonOfficeAttachmentName(name, ext)) {
|
|
|
|
|
|
return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-code';
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-file';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedAttachmentClass(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedAttachmentClass');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
return attachmentClassForFileName(uploadedAssetTitle(asset));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildOnlyOfficeAssetOpenUrl(asset, userId) {
|
|
|
|
|
|
var title = uploadedAssetTitle(asset);
|
|
|
|
|
|
var fileType = inferOnlyOfficeFileType(title, asset && asset.mime_type);
|
|
|
|
|
|
if (!fileType) return '';
|
|
|
|
|
|
var assetId = String(asset && asset.id || '').trim();
|
|
|
|
|
|
if (isLocalUploadedAsset(asset)) {
|
|
|
|
|
|
var localUrl = localAssetOpenUrl(asset, false);
|
|
|
|
|
|
if (!localUrl) return '';
|
|
|
|
|
|
return buildOnlyOfficeOpenUrl({
|
|
|
|
|
|
fileUrl: localUrl,
|
|
|
|
|
|
fileName: title,
|
|
|
|
|
|
fileType: fileType,
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
|
|
|
|
|
userId: userId || '',
|
|
|
|
|
|
mode: 'view'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return buildOnlyOfficeOpenUrl({
|
|
|
|
|
|
fileUrl: assetId ? '' : uploadedAssetUrl(asset),
|
|
|
|
|
|
fileName: title,
|
|
|
|
|
|
fileType: fileType,
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
|
|
|
|
|
userId: userId || '',
|
|
|
|
|
|
mode: 'view'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uploadedFileSize(asset) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadedFileSize');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset);
|
|
|
|
|
|
var size = Number(asset && (asset.file_size || asset.fileSize) || 0);
|
|
|
|
|
|
if (!Number.isFinite(size) || size <= 0) return '';
|
|
|
|
|
|
if (size >= 1024 * 1024) return (size / 1024 / 1024).toFixed(size >= 10 * 1024 * 1024 ? 1 : 2) + ' MB';
|
|
|
|
|
|
if (size >= 1024) return (size / 1024).toFixed(size >= 100 * 1024 ? 0 : 2) + ' KB';
|
|
|
|
|
|
return String(Math.round(size)) + ' B';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var attachmentMetaCache = Object.create(null);
|
|
|
|
|
|
var attachmentMetaPending = Object.create(null);
|
|
|
|
|
|
var legacyOfficeAttachmentIndex = null;
|
|
|
|
|
|
var legacyOfficeAttachmentIndexPending = null;
|
|
|
|
|
|
|
|
|
|
|
|
function parseCurrentWorkspaceId() {
|
|
|
|
|
|
return (new URLSearchParams(window.location.search).get('workspaceId') || '').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function fetchLegacyOfficeAttachmentIndex() {
|
|
|
|
|
|
if (legacyOfficeAttachmentIndex) return legacyOfficeAttachmentIndex;
|
|
|
|
|
|
if (legacyOfficeAttachmentIndexPending) return legacyOfficeAttachmentIndexPending;
|
|
|
|
|
|
var documentId = currentDocumentId();
|
|
|
|
|
|
var workspaceId = parseCurrentWorkspaceId();
|
|
|
|
|
|
if (!documentId || !workspaceId) {
|
|
|
|
|
|
legacyOfficeAttachmentIndex = Object.create(null);
|
|
|
|
|
|
return legacyOfficeAttachmentIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
var projectionUrl = new URL('/api/tree/projections/file', window.location.origin);
|
|
|
|
|
|
projectionUrl.searchParams.set('documentId', documentId);
|
|
|
|
|
|
projectionUrl.searchParams.set('workspaceId', workspaceId);
|
|
|
|
|
|
var sourcePayload = currentWorkspaceSourcePayload();
|
|
|
|
|
|
if (sourcePayload.sourceKind) projectionUrl.searchParams.set('sourceKind', sourcePayload.sourceKind);
|
|
|
|
|
|
if (sourcePayload.rootUri) projectionUrl.searchParams.set('rootUri', sourcePayload.rootUri);
|
|
|
|
|
|
legacyOfficeAttachmentIndexPending = fetch(projectionUrl.toString(), {
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
cache: 'no-store'
|
|
|
|
|
|
}).then(function(response) {
|
|
|
|
|
|
return response.json().catch(function() { return null; }).then(function(payload) {
|
|
|
|
|
|
var items = payload && payload.ok && payload.result && Array.isArray(payload.result.items)
|
|
|
|
|
|
? payload.result.items
|
|
|
|
|
|
: [];
|
|
|
|
|
|
var index = Object.create(null);
|
|
|
|
|
|
items.forEach(function(item) {
|
|
|
|
|
|
if (!item || item.rowKind !== 'asset') return;
|
|
|
|
|
|
var title = String(item.title || '').trim();
|
|
|
|
|
|
if (!title || index[title]) return;
|
|
|
|
|
|
var fileType = inferOnlyOfficeFileType(title, '');
|
|
|
|
|
|
if (!fileType) return;
|
|
|
|
|
|
var rowId = String(item.rowId || '').trim();
|
|
|
|
|
|
var assetId = String(item.assetId || '').trim();
|
|
|
|
|
|
if (!assetId && rowId.indexOf('asset:') === 0) assetId = rowId.slice('asset:'.length);
|
|
|
|
|
|
if (!assetId) return;
|
|
|
|
|
|
index[title] = {
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
fileName: title,
|
|
|
|
|
|
fileType: fileType,
|
|
|
|
|
|
documentId: documentId
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
legacyOfficeAttachmentIndex = index;
|
|
|
|
|
|
return index;
|
|
|
|
|
|
});
|
|
|
|
|
|
}).catch(function() {
|
|
|
|
|
|
var empty = Object.create(null);
|
|
|
|
|
|
legacyOfficeAttachmentIndex = empty;
|
|
|
|
|
|
return empty;
|
|
|
|
|
|
}).finally(function() {
|
|
|
|
|
|
legacyOfficeAttachmentIndexPending = null;
|
|
|
|
|
|
});
|
|
|
|
|
|
return legacyOfficeAttachmentIndexPending;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function healLegacyOfficeAttachmentParagraphs() {
|
|
|
|
|
|
var editor = document.querySelector('.editor-surface .ProseMirror');
|
|
|
|
|
|
if (!(editor instanceof HTMLElement)) return;
|
|
|
|
|
|
var paragraphs = Array.from(editor.querySelectorAll('p'));
|
2026-05-27 11:31:12 +08:00
|
|
|
|
var candidates = paragraphs.filter(function(paragraph) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (!(paragraph instanceof HTMLParagraphElement)) return;
|
|
|
|
|
|
if (paragraph.querySelector('a, img, video, audio, table, iframe, canvas')) return;
|
|
|
|
|
|
if (paragraph.childNodes.length !== 1 || paragraph.firstChild?.nodeType !== Node.TEXT_NODE) return;
|
|
|
|
|
|
var fileName = String(paragraph.textContent || '').trim();
|
|
|
|
|
|
if (!fileName) return;
|
2026-05-27 11:31:12 +08:00
|
|
|
|
return Boolean(inferOnlyOfficeFileType(fileName, ''));
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!candidates.length) return;
|
|
|
|
|
|
var index = await fetchLegacyOfficeAttachmentIndex();
|
|
|
|
|
|
candidates.forEach(function(paragraph) {
|
|
|
|
|
|
var fileName = String(paragraph.textContent || '').trim();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var detail = index[fileName];
|
|
|
|
|
|
if (!detail) return;
|
|
|
|
|
|
var link = document.createElement('a');
|
|
|
|
|
|
link.textContent = fileName;
|
|
|
|
|
|
link.setAttribute('href', buildOnlyOfficeOpenPath({
|
|
|
|
|
|
fileUrl: '',
|
|
|
|
|
|
fileName: detail.fileName,
|
|
|
|
|
|
fileType: detail.fileType,
|
|
|
|
|
|
assetId: detail.assetId,
|
|
|
|
|
|
documentId: detail.documentId || currentDocumentId() || '',
|
|
|
|
|
|
userId: '',
|
|
|
|
|
|
mode: 'view'
|
|
|
|
|
|
}));
|
|
|
|
|
|
link.setAttribute('data-asset-id', detail.assetId);
|
|
|
|
|
|
attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) {
|
|
|
|
|
|
if (name) link.classList.add(name);
|
|
|
|
|
|
});
|
|
|
|
|
|
paragraph.replaceChildren(link);
|
|
|
|
|
|
enhanceEditorAttachmentLink(link);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyEditorAttachmentMeta(link, meta) {
|
|
|
|
|
|
if (!(link instanceof HTMLAnchorElement) || !meta) return;
|
|
|
|
|
|
if (meta.assetId) link.setAttribute('data-asset-id', meta.assetId);
|
|
|
|
|
|
if (meta.fileSize) link.setAttribute('data-file-size', meta.fileSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function hydrateEditorAttachmentMeta(link) {
|
|
|
|
|
|
if (!(link instanceof HTMLAnchorElement)) return;
|
|
|
|
|
|
var detail = detailFromEditorAttachmentLink(link);
|
|
|
|
|
|
var assetId = String(detail && detail.assetId || '').trim();
|
|
|
|
|
|
if (!assetId) return;
|
|
|
|
|
|
var localFilePath = localFilePathFromAssetId(assetId);
|
|
|
|
|
|
if (localFilePath) {
|
|
|
|
|
|
var localMeta = {
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
fileSize: String(detail && detail.fileSize || '').trim()
|
|
|
|
|
|
};
|
|
|
|
|
|
attachmentMetaCache[assetId] = localMeta;
|
|
|
|
|
|
applyEditorAttachmentMeta(link, localMeta);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (attachmentMetaCache[assetId]) {
|
|
|
|
|
|
applyEditorAttachmentMeta(link, attachmentMetaCache[assetId]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (attachmentMetaPending[assetId]) {
|
|
|
|
|
|
try { await attachmentMetaPending[assetId]; } catch (_) {}
|
|
|
|
|
|
applyEditorAttachmentMeta(link, attachmentMetaCache[assetId]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-07 10:35:21 +08:00
|
|
|
|
document.documentElement.setAttribute('data-mnote-media-sign-retired', 'true');
|
|
|
|
|
|
attachmentMetaPending[assetId] = Promise.resolve(null).finally(function() {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
delete attachmentMetaPending[assetId];
|
|
|
|
|
|
});
|
|
|
|
|
|
try {
|
|
|
|
|
|
var meta = await attachmentMetaPending[assetId];
|
|
|
|
|
|
applyEditorAttachmentMeta(link, meta);
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function revealFileTreeRow(row) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('revealFileTreeRow');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return;
|
|
|
|
|
|
var node = row.closest('.tree-node');
|
|
|
|
|
|
while (node && node.parentElement) {
|
|
|
|
|
|
if (node.parentElement.classList && node.parentElement.classList.contains('tree-children')) {
|
|
|
|
|
|
node.parentElement.classList.remove('tree-children--collapsed');
|
|
|
|
|
|
var parentNode = node.parentElement.closest('.tree-node');
|
|
|
|
|
|
var parentRow = parentNode ? parentNode.querySelector(':scope > .tree-row') : null;
|
|
|
|
|
|
if (parentRow instanceof HTMLElement) {
|
|
|
|
|
|
parentRow.setAttribute('aria-expanded', 'true');
|
|
|
|
|
|
var toggle = parentRow.querySelector('[data-rust-action="toggle"]');
|
|
|
|
|
|
if (toggle) toggle.setAttribute('aria-expanded', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
node = node.parentElement.closest('.tree-node');
|
|
|
|
|
|
}
|
|
|
|
|
|
try { row.scrollIntoView({ block: 'nearest' }); } catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function revealFileTreeAssetRow(assetId) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('revealFileTreeAssetRow');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(assetId, fileTreeRuntimeDeps());
|
|
|
|
|
|
if (!assetId) return false;
|
|
|
|
|
|
var row = document.querySelector('.tree-row[data-shell-mode="filetree"][data-asset-id="' + cssEscape(assetId) + '"]');
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return false;
|
|
|
|
|
|
revealFileTreeRow(row);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function appendUploadedAssetRow(asset, documentId) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('appendUploadedAssetRow');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(asset, documentId, fileTreeRuntimeDeps());
|
|
|
|
|
|
var assetId = String(asset && asset.id || '').trim();
|
|
|
|
|
|
if (!assetId) return false;
|
|
|
|
|
|
if (revealFileTreeAssetRow(assetId)) return true;
|
|
|
|
|
|
var objectKind = String(asset && (asset.objectKind || asset.resourceKind || '') || '').trim();
|
|
|
|
|
|
if (!objectKind && (String(asset && (asset.asset_type || asset.assetType) || '').trim() === 'mindmap' || /\.mindmap\.json$/i.test(assetId))) {
|
|
|
|
|
|
objectKind = 'mindmap';
|
|
|
|
|
|
}
|
|
|
|
|
|
var targetDocumentId = String(documentId || asset.document_id || asset.documentId || currentDocumentId() || '').trim();
|
|
|
|
|
|
var parentRow = targetDocumentId
|
|
|
|
|
|
? document.querySelector('.tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + targetDocumentId) + '"]')
|
|
|
|
|
|
: null;
|
|
|
|
|
|
if (!parentRow && currentSourceKind() === 'local_folder' && objectKind === 'mindmap') return false;
|
|
|
|
|
|
if (!parentRow) parentRow = document.querySelector('.tree-row[data-shell-mode="filetree"][data-row-kind="document"]');
|
|
|
|
|
|
var root = document.querySelector('#sidebar-file-tree-root .tree-root');
|
|
|
|
|
|
if (!root && !parentRow) return false;
|
|
|
|
|
|
var parentLi = parentRow ? parentRow.closest('.tree-node') : null;
|
|
|
|
|
|
var children = parentLi ? parentLi.querySelector(':scope > .tree-children') : null;
|
|
|
|
|
|
if (parentLi && !children) {
|
|
|
|
|
|
children = document.createElement('ul');
|
|
|
|
|
|
children.className = 'tree-children';
|
|
|
|
|
|
parentLi.appendChild(children);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (children) {
|
|
|
|
|
|
children.classList.remove('tree-children--collapsed');
|
|
|
|
|
|
if (parentRow) {
|
|
|
|
|
|
parentRow.setAttribute('aria-expanded', 'true');
|
|
|
|
|
|
var toggle = parentRow.querySelector('[data-rust-action="toggle"]');
|
|
|
|
|
|
if (toggle) toggle.setAttribute('aria-expanded', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var container = children || root;
|
|
|
|
|
|
var li = document.createElement('li');
|
|
|
|
|
|
li.className = 'tree-node';
|
|
|
|
|
|
var objectIdentity = {
|
|
|
|
|
|
objectKind: objectKind || 'attachment',
|
|
|
|
|
|
documentId: targetDocumentId || null,
|
|
|
|
|
|
blockId: null,
|
|
|
|
|
|
assetId: assetId
|
|
|
|
|
|
};
|
|
|
|
|
|
li.setAttribute('data-node-id', 'asset:' + assetId);
|
|
|
|
|
|
var title = uploadedAssetTitle(asset);
|
|
|
|
|
|
var iconKind = fileTreeIconKindForFileName(title) || uploadedAssetType(asset) || 'file';
|
|
|
|
|
|
if (objectKind === 'mindmap') iconKind = 'mindmap';
|
|
|
|
|
|
li.innerHTML =
|
|
|
|
|
|
'<div class="tree-row" role="treeitem" aria-level="' + (parentRow ? '2' : '1') + '" aria-expanded="false" data-rust-rendered-row="filetree" data-testid="filetree-asset-row" data-row-id="' + escapeHtml('asset:' + assetId) + '" data-row-kind="asset" data-node-id="' + escapeHtml('asset:' + assetId) + '" data-document-id="' + escapeHtml(targetDocumentId) + '" data-doc-id="' + escapeHtml(targetDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(objectKind || 'attachment') + '" data-shell-mode="filetree" data-selected="false" data-active="false" draggable="true">' +
|
|
|
|
|
|
'<span class="tree-spacer" aria-hidden="true"></span><span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span>' +
|
|
|
|
|
|
'<button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml('asset:' + assetId) + '" data-document-id="' + escapeHtml(targetDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button>' +
|
|
|
|
|
|
'<div class="tree-actions"><button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml('asset:' + assetId) + '" aria-label="更多操作">…</button></div></div>';
|
|
|
|
|
|
container.appendChild(li);
|
|
|
|
|
|
revealFileTreeRow(li.querySelector('.tree-row'));
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-asset-id', assetId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeFileTreeAssetRow(assetId) {
|
|
|
|
|
|
var normalized = String(assetId || '').trim();
|
|
|
|
|
|
if (!normalized) return false;
|
|
|
|
|
|
var row = document.querySelector('.tree-row[data-shell-mode="filetree"][data-asset-id="' + cssEscape(normalized) + '"]');
|
|
|
|
|
|
var node = row ? row.closest('.tree-node') : null;
|
|
|
|
|
|
if (!node || !node.parentElement) return false;
|
|
|
|
|
|
node.parentElement.removeChild(node);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyAssetsChangedToFileTree(detail) {
|
|
|
|
|
|
detail = detail || {};
|
|
|
|
|
|
var docId = String(detail.docId || detail.documentId || currentDocumentId() || '').trim();
|
|
|
|
|
|
var changed = false;
|
|
|
|
|
|
var explicitAsset = detail.asset && typeof detail.asset === 'object' ? detail.asset : null;
|
|
|
|
|
|
if (explicitAsset) {
|
|
|
|
|
|
appendUploadedAssetRow(explicitAsset, docId);
|
|
|
|
|
|
changed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
var mindmapAssetIds = Array.isArray(detail.mindmapAssetIds) ? detail.mindmapAssetIds : [];
|
|
|
|
|
|
if (detail.mindmapDeleted === true) {
|
|
|
|
|
|
mindmapAssetIds.forEach(function(assetId) {
|
|
|
|
|
|
if (removeFileTreeAssetRow(assetId)) changed = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-assets-local-applied', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function mindmapAssetFromTarget(documentId, mindmapId, writeResult) {
|
|
|
|
|
|
var docId = String(documentId || currentDocumentId() || '').trim();
|
|
|
|
|
|
var assetId = String(mindmapId || '').trim();
|
|
|
|
|
|
if (!docId || !assetId) return null;
|
|
|
|
|
|
var result = writeResult && typeof writeResult === 'object' ? writeResult : {};
|
|
|
|
|
|
var resultAssetId = String(result.assetId || result.id || '').trim();
|
|
|
|
|
|
var relativePath = String(result.relativePath || result.rootRelativePath || '').trim();
|
|
|
|
|
|
var fileName = String(result.fileName || '').trim() || shortMindmapFileName(assetId);
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: resultAssetId || (relativePath ? 'local-file:' + relativePath : assetId),
|
|
|
|
|
|
document_id: docId,
|
|
|
|
|
|
asset_type: 'mindmap',
|
|
|
|
|
|
file_name: fileName,
|
|
|
|
|
|
file_url: relativePath || ('/documents/' + encodeURIComponent(docId) + '/' + encodeURIComponent(fileName)),
|
|
|
|
|
|
sourcePath: relativePath,
|
|
|
|
|
|
rootRelativePath: relativePath,
|
|
|
|
|
|
sourceKind: currentSourceKind(),
|
|
|
|
|
|
rootUri: currentRootUri(),
|
|
|
|
|
|
objectKind: 'mindmap'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function shortMindmapFileName(mindmapId) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('shortMindmapFileName');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(mindmapId);
|
|
|
|
|
|
var raw = String(mindmapId || '').trim();
|
|
|
|
|
|
var pathName = raw.indexOf('/') >= 0 ? raw.split('/').pop() : raw;
|
|
|
|
|
|
if (/\.json$/i.test(pathName)) return pathName;
|
|
|
|
|
|
var digits = raw.match(/(\d{4,})$/);
|
|
|
|
|
|
var suffix = digits ? digits[1].slice(-6) : '';
|
|
|
|
|
|
return suffix ? '思维导图' + suffix + '.json' : '思维导图.json';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseMindmapApiTarget(input) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('parseMindmapApiTarget');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(input);
|
|
|
|
|
|
try {
|
|
|
|
|
|
var rawUrl = typeof input === 'string'
|
|
|
|
|
|
? input
|
|
|
|
|
|
: input && typeof input.url === 'string'
|
|
|
|
|
|
? input.url
|
|
|
|
|
|
: '';
|
|
|
|
|
|
if (!rawUrl) return null;
|
|
|
|
|
|
var url = new URL(rawUrl, window.location.origin);
|
|
|
|
|
|
var match = /^\/api\/mindmap\/([^/]+)\/([^/]+)$/.exec(url.pathname);
|
|
|
|
|
|
if (!match) return null;
|
|
|
|
|
|
return {
|
|
|
|
|
|
documentId: decodeURIComponent(match[1]),
|
|
|
|
|
|
mindmapId: decodeURIComponent(match[2])
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function withLocalMindmapSourceParams(input) {
|
|
|
|
|
|
var target = parseMindmapApiTarget(input);
|
|
|
|
|
|
if (!target || currentSourceKind() !== 'local_folder') return input;
|
|
|
|
|
|
var rootUri = currentRootUri();
|
|
|
|
|
|
if (!rootUri) return input;
|
|
|
|
|
|
var rawUrl = typeof input === 'string'
|
|
|
|
|
|
? input
|
|
|
|
|
|
: input && typeof input.url === 'string'
|
|
|
|
|
|
? input.url
|
|
|
|
|
|
: '';
|
|
|
|
|
|
if (!rawUrl) return input;
|
|
|
|
|
|
var url = new URL(rawUrl, window.location.origin);
|
|
|
|
|
|
url.searchParams.set('sourceKind', 'local_folder');
|
|
|
|
|
|
url.searchParams.set('rootUri', rootUri);
|
|
|
|
|
|
if (typeof input === 'string') return url.pathname + url.search;
|
|
|
|
|
|
if (typeof Request !== 'undefined' && input instanceof Request) {
|
|
|
|
|
|
return new Request(url.toString(), input);
|
|
|
|
|
|
}
|
|
|
|
|
|
return url.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function requestMethod(input, init) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('requestMethod');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(input, init);
|
|
|
|
|
|
return String(
|
|
|
|
|
|
init && init.method
|
|
|
|
|
|
? init.method
|
|
|
|
|
|
: input && typeof input.method === 'string'
|
|
|
|
|
|
? input.method
|
|
|
|
|
|
: 'GET'
|
|
|
|
|
|
).toUpperCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function requestBodyHasMindmapCreateOnly(init) {
|
|
|
|
|
|
var runtimeFn = fileTreeRuntimeFunction('requestBodyHasMindmapCreateOnly');
|
|
|
|
|
|
if (runtimeFn) return runtimeFn(init);
|
|
|
|
|
|
var body = init && typeof init.body === 'string' ? init.body : '';
|
|
|
|
|
|
if (!body) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
var payload = JSON.parse(body);
|
|
|
|
|
|
return payload && payload.createOnly === true;
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyMindmapApiMutationToFileTree(target, payload) {
|
|
|
|
|
|
if (!target || !target.documentId || !target.mindmapId) return;
|
|
|
|
|
|
var asset = mindmapAssetFromTarget(target.documentId, target.mindmapId, payload && payload.writeResult);
|
|
|
|
|
|
if (!asset) return;
|
|
|
|
|
|
var appended = appendUploadedAssetRow(asset, target.documentId);
|
|
|
|
|
|
if (!appended && currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-assets-local-applied', 'true');
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', asset.id || target.mindmapId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function installMindmapAssetFetchObserver() {
|
|
|
|
|
|
if (window.__mnoteMindmapAssetFetchObserverInstalled === true) return;
|
|
|
|
|
|
if (typeof window.fetch !== 'function') return;
|
|
|
|
|
|
window.__mnoteMindmapAssetFetchObserverInstalled = true;
|
|
|
|
|
|
var originalFetch = window.fetch.bind(window);
|
|
|
|
|
|
window.fetch = function(input, init) {
|
|
|
|
|
|
var nextInput = withLocalMindmapSourceParams(input);
|
|
|
|
|
|
var target = parseMindmapApiTarget(nextInput);
|
|
|
|
|
|
var method = requestMethod(input, init);
|
|
|
|
|
|
var createOnly = requestBodyHasMindmapCreateOnly(init || {});
|
|
|
|
|
|
return originalFetch(nextInput, init).then(function(response) {
|
|
|
|
|
|
if (target && method === 'POST' && response && response.ok) {
|
|
|
|
|
|
var cloned = response.clone();
|
|
|
|
|
|
void cloned.json().then(function(payload) {
|
|
|
|
|
|
if (createOnly || target.documentId === currentDocumentId()) {
|
|
|
|
|
|
applyMindmapApiMutationToFileTree(target, payload);
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(function() {
|
|
|
|
|
|
if (createOnly || target.documentId === currentDocumentId()) {
|
|
|
|
|
|
applyMindmapApiMutationToFileTree(target, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return response;
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function archiveLocalFileTreeAsset(row, assetId) {
|
|
|
|
|
|
if (!assetId) return false;
|
|
|
|
|
|
await dispatchTreeCommand(row || document.body, {
|
|
|
|
|
|
action: 'archive',
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(row || document.body),
|
|
|
|
|
|
documentId: assetId
|
|
|
|
|
|
});
|
|
|
|
|
|
removeFileTreeAssetRow(assetId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteSingleFileTreeAsset(detail, trigger) {
|
|
|
|
|
|
var assetId = String(detail && detail.assetId || '').trim();
|
|
|
|
|
|
if (!assetId) return false;
|
|
|
|
|
|
var row = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : null;
|
|
|
|
|
|
if (!row) row = document.querySelector('.tree-row[data-shell-mode="filetree"][data-asset-id="' + cssEscape(assetId) + '"]');
|
|
|
|
|
|
if (currentSourceKind() === 'local_folder') {
|
|
|
|
|
|
return archiveLocalFileTreeAsset(row, assetId);
|
|
|
|
|
|
}
|
|
|
|
|
|
var documentId = String(detail && detail.documentId || '').trim();
|
|
|
|
|
|
var kind = classifySidebarFileTreeAsset(row);
|
|
|
|
|
|
if (kind === 'mindmap') {
|
|
|
|
|
|
var response = await fetch('/api/mindmap/' + encodeURIComponent(documentId) + '/' + encodeURIComponent(assetId), { method: 'DELETE' });
|
|
|
|
|
|
if (!response.ok) throw new Error('mindmap_delete_failed_' + response.status);
|
|
|
|
|
|
removeFileTreeAssetRow(assetId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (kind === 'table') {
|
|
|
|
|
|
var tableResponse = await fetch('/api/tables/' + encodeURIComponent(assetId), { method: 'DELETE' });
|
|
|
|
|
|
if (!tableResponse.ok) throw new Error('table_delete_failed_' + tableResponse.status);
|
|
|
|
|
|
removeFileTreeAssetRow(assetId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
await postSidebarFileTreeJson('/api/media/batch', { action: 'delete', assetIds: [assetId] });
|
|
|
|
|
|
removeFileTreeAssetRow(assetId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveEditorUploadContext(detail) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('resolveEditorUploadContext');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return runtimeFn(detail || {}, {
|
|
|
|
|
|
rootSelector: EDITOR_UPLOAD_ROOT_SELECTOR,
|
|
|
|
|
|
editorUploadRootFromElement: editorUploadRootFromElement,
|
|
|
|
|
|
currentDocumentId: currentDocumentId,
|
|
|
|
|
|
resolveWorkspaceId: resolveWorkspaceId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
var root = null;
|
|
|
|
|
|
var selector = detail && detail.editorRootSelector ? String(detail.editorRootSelector) : '';
|
|
|
|
|
|
if (selector) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var selected = document.querySelector(selector);
|
|
|
|
|
|
if (selected instanceof HTMLElement) root = selected;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!root && window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) {
|
|
|
|
|
|
root = window.__mnoteIntendedSlashRoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!root && window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) {
|
|
|
|
|
|
root = window.__mnoteLastEditorUploadRoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!root && document.activeElement instanceof Element) {
|
|
|
|
|
|
root = editorUploadRootFromElement(document.activeElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!root) {
|
|
|
|
|
|
var focused = document.querySelector(EDITOR_UPLOAD_ROOT_SELECTOR + ' .ProseMirror:focus-within');
|
|
|
|
|
|
root = editorUploadRootFromElement(focused);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!root) {
|
|
|
|
|
|
root = document.querySelector('[data-editor-host-kind="leptos_tiptap_island"][data-pane-role="primary"]');
|
|
|
|
|
|
}
|
|
|
|
|
|
var pane = root instanceof Element ? root.closest('.document-pane[data-pane-role]') : null;
|
|
|
|
|
|
var shell = root instanceof Element ? root.closest('.document-shell[data-document-id]') : null;
|
|
|
|
|
|
return {
|
|
|
|
|
|
root: root instanceof HTMLElement ? root : null,
|
|
|
|
|
|
documentId: String(
|
|
|
|
|
|
detail && detail.documentId
|
|
|
|
|
|
|| (root instanceof HTMLElement && root.getAttribute('data-document-id'))
|
|
|
|
|
|
|| (pane instanceof HTMLElement && pane.getAttribute('data-pane-document-id'))
|
|
|
|
|
|
|| (shell instanceof HTMLElement && shell.getAttribute('data-document-id'))
|
|
|
|
|
|
|| currentDocumentId()
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim(),
|
|
|
|
|
|
workspaceId: String(
|
|
|
|
|
|
detail && detail.workspaceId
|
|
|
|
|
|
|| (root instanceof HTMLElement && root.getAttribute('data-workspace-id'))
|
|
|
|
|
|
|| (pane instanceof HTMLElement && pane.getAttribute('data-pane-workspace-id'))
|
|
|
|
|
|
|| (shell instanceof HTMLElement && shell.getAttribute('data-workspace-id'))
|
|
|
|
|
|
|| resolveWorkspaceId(document.body)
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function editorRootFromUploadOptions(options) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('editorRootFromUploadOptions');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return runtimeFn(options || {}, {
|
|
|
|
|
|
rootSelector: EDITOR_UPLOAD_ROOT_SELECTOR,
|
|
|
|
|
|
editorUploadRootFromElement: editorUploadRootFromElement
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (options && options.editorRoot instanceof HTMLElement) return options.editorRoot;
|
|
|
|
|
|
if (window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) {
|
|
|
|
|
|
return window.__mnoteIntendedSlashRoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) {
|
|
|
|
|
|
return window.__mnoteLastEditorUploadRoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
var focused = document.querySelector(EDITOR_UPLOAD_ROOT_SELECTOR + ' .ProseMirror:focus-within');
|
|
|
|
|
|
return editorUploadRootFromElement(focused);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function fetchWithTimeout(input, init, timeoutMs, label) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('fetchWithTimeout');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return await runtimeFn(input, init || {}, timeoutMs, label);
|
|
|
|
|
|
}
|
|
|
|
|
|
var controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
|
|
|
|
var timer = 0;
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (controller) {
|
|
|
|
|
|
timer = window.setTimeout(function() {
|
|
|
|
|
|
controller.abort();
|
|
|
|
|
|
}, Math.max(1000, Number(timeoutMs) || 15000));
|
|
|
|
|
|
}
|
|
|
|
|
|
var nextInit = Object.assign({}, init || {});
|
|
|
|
|
|
if (controller) nextInit.signal = controller.signal;
|
|
|
|
|
|
return await fetch(input, nextInit);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error && error.name === 'AbortError') {
|
|
|
|
|
|
throw new Error((label || '请求') + '超时');
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (timer) window.clearTimeout(timer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function insertUploadedAssetIntoEditor(asset, targetRoot) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('insertUploadedAssetIntoEditor');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return await runtimeFn(asset, targetRoot, {
|
|
|
|
|
|
uploadedAssetTitle: uploadedAssetTitle,
|
|
|
|
|
|
localAssetOpenUrl: localAssetOpenUrl,
|
|
|
|
|
|
uploadedAssetUrl: uploadedAssetUrl,
|
|
|
|
|
|
uploadedAssetType: uploadedAssetType,
|
|
|
|
|
|
isLocalUploadedAsset: isLocalUploadedAsset,
|
|
|
|
|
|
buildOnlyOfficeAssetOpenUrl: buildOnlyOfficeAssetOpenUrl,
|
|
|
|
|
|
fetchCurrentOnlyOfficeUserId: fetchCurrentOnlyOfficeUserId,
|
|
|
|
|
|
buildOnlyOfficeOpenPath: buildOnlyOfficeOpenPath,
|
|
|
|
|
|
inferOnlyOfficeFileType: inferOnlyOfficeFileType,
|
|
|
|
|
|
currentDocumentId: currentDocumentId,
|
2026-05-26 09:44:35 +08:00
|
|
|
|
currentRootUri: currentRootUri,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
uploadedAttachmentClass: uploadedAttachmentClass,
|
|
|
|
|
|
uploadedFileSize: uploadedFileSize,
|
|
|
|
|
|
cssEscape: cssEscape,
|
|
|
|
|
|
enhanceEditorAttachmentLinks: enhanceEditorAttachmentLinks
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
var editorRoot = targetRoot instanceof HTMLElement
|
|
|
|
|
|
? targetRoot.querySelector('.editor-surface .ProseMirror')
|
|
|
|
|
|
: document.querySelector('.editor-surface .ProseMirror');
|
|
|
|
|
|
if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot;
|
|
|
|
|
|
var editor = editorRoot && editorRoot.editor;
|
|
|
|
|
|
if (!editor || !editor.chain) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'editor_unavailable');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
var title = uploadedAssetTitle(asset);
|
2026-05-29 11:13:05 +08:00
|
|
|
|
var markdownHref = uploadedAssetMarkdownHref(asset);
|
2026-06-02 17:17:49 +08:00
|
|
|
|
var localOpenUrl = localAssetOpenUrl(asset, false);
|
|
|
|
|
|
var fallbackUrl = uploadedAssetUrl(asset);
|
|
|
|
|
|
var imageUrl = localOpenUrl || fallbackUrl || markdownHref;
|
|
|
|
|
|
var url = markdownHref || localOpenUrl || fallbackUrl;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var type = uploadedAssetType(asset);
|
|
|
|
|
|
var assetId = String(asset && asset.id || '').trim();
|
|
|
|
|
|
var sizeLabel = uploadedFileSize(asset);
|
|
|
|
|
|
try {
|
2026-06-02 17:17:49 +08:00
|
|
|
|
if (type === 'image' && imageUrl) {
|
|
|
|
|
|
return editor.chain().focus().setImage({ src: imageUrl, alt: title, title: title }).run() === true;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
2026-05-29 11:13:05 +08:00
|
|
|
|
var href = markdownHref || url;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (href) {
|
|
|
|
|
|
var inserted = editor.chain().focus().insertContent([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'paragraph',
|
|
|
|
|
|
content: [{
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
text: title,
|
|
|
|
|
|
marks: [{
|
|
|
|
|
|
type: 'link',
|
|
|
|
|
|
attrs: {
|
2026-05-29 11:13:05 +08:00
|
|
|
|
href: href,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
target: '_blank',
|
|
|
|
|
|
rel: 'noopener noreferrer nofollow',
|
|
|
|
|
|
class: uploadedAttachmentClass(asset)
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
}]
|
|
|
|
|
|
},
|
|
|
|
|
|
{ type: 'paragraph' }
|
|
|
|
|
|
]).focus('end').run() === true;
|
|
|
|
|
|
if (inserted) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'true');
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-asset-id', assetId || '');
|
|
|
|
|
|
document.documentElement.removeAttribute('data-mnote-last-upload-insert-error');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'insert_content_failed');
|
|
|
|
|
|
}
|
2026-05-28 22:01:44 +08:00
|
|
|
|
window.setTimeout(function annotateUploadedLink(attempt) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot;
|
|
|
|
|
|
enhanceEditorAttachmentLinks();
|
|
|
|
|
|
var selector = assetId
|
|
|
|
|
|
? '.editor-surface .ProseMirror a[href*="' + cssEscape(assetId) + '"]'
|
|
|
|
|
|
: '.editor-surface .ProseMirror a';
|
|
|
|
|
|
var link = targetRoot instanceof HTMLElement
|
|
|
|
|
|
? targetRoot.querySelector(selector)
|
|
|
|
|
|
: document.querySelector(selector);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
if (!(link instanceof HTMLElement) && assetId) {
|
|
|
|
|
|
var scope = targetRoot instanceof HTMLElement ? targetRoot : document;
|
|
|
|
|
|
link = Array.from(scope.querySelectorAll('.editor-surface .ProseMirror a')).find(function(candidate) {
|
|
|
|
|
|
var href = String(candidate.getAttribute('href') || '');
|
|
|
|
|
|
try { href = decodeURIComponent(href); } catch (_) {}
|
|
|
|
|
|
return href.indexOf(assetId) >= 0;
|
|
|
|
|
|
}) || null;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (link instanceof HTMLElement) {
|
|
|
|
|
|
if (assetId) link.setAttribute('data-asset-id', assetId);
|
|
|
|
|
|
if (sizeLabel) link.setAttribute('data-file-size', sizeLabel);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
} else if (Number(attempt) < 20) {
|
|
|
|
|
|
window.setTimeout(function() { annotateUploadedLink(Number(attempt) + 1); }, 50);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
2026-05-28 22:01:44 +08:00
|
|
|
|
}, 0, 0);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return inserted;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('[mnote upload] insert uploaded asset failed', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function uploadFileToMediaAsset(file, plan, options) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadFileToMediaAsset');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return await runtimeFn(file, plan, options || {}, {
|
|
|
|
|
|
currentSourceKind: currentSourceKind,
|
|
|
|
|
|
currentRootUri: currentRootUri,
|
|
|
|
|
|
currentDocumentId: currentDocumentId,
|
|
|
|
|
|
editorRootFromUploadOptions: editorRootFromUploadOptions,
|
|
|
|
|
|
appendUploadedAssetRow: appendUploadedAssetRow,
|
|
|
|
|
|
refreshLocalFolderSidebarSnapshot: refreshLocalFolderSidebarSnapshot,
|
|
|
|
|
|
dispatchEvent: function(event) { return window.dispatchEvent(event); },
|
|
|
|
|
|
CustomEvent: CustomEvent,
|
|
|
|
|
|
buildOnlyOfficeAssetOpenUrl: buildOnlyOfficeAssetOpenUrl,
|
|
|
|
|
|
fetchCurrentOnlyOfficeUserId: fetchCurrentOnlyOfficeUserId,
|
|
|
|
|
|
buildOnlyOfficeOpenPath: buildOnlyOfficeOpenPath,
|
|
|
|
|
|
inferOnlyOfficeFileType: inferOnlyOfficeFileType,
|
|
|
|
|
|
enhanceEditorAttachmentLinks: enhanceEditorAttachmentLinks,
|
|
|
|
|
|
cssEscape: cssEscape
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (currentSourceKind() === 'local_folder') {
|
|
|
|
|
|
var rootUri = currentRootUri();
|
|
|
|
|
|
var documentId = String(plan && plan.targetDocumentId || currentDocumentId() || '').trim();
|
|
|
|
|
|
var hasFolderTarget = plan && Object.prototype.hasOwnProperty.call(plan, 'targetRelativePath');
|
|
|
|
|
|
var uploadIntent = String(plan && plan.uploadIntent || (hasFolderTarget ? 'filetree.folder.drop' : 'editor.markdown.attach')).trim();
|
|
|
|
|
|
if (!rootUri || !uploadIntent) {
|
|
|
|
|
|
throw new Error('本地 Markdown 上传缺少 rootUri 或 documentId');
|
|
|
|
|
|
}
|
|
|
|
|
|
var localUploadRuntime = localUploadRuntimeFunction('uploadLocalFolderAsset');
|
|
|
|
|
|
var localPayload = null;
|
|
|
|
|
|
if (localUploadRuntime) {
|
|
|
|
|
|
var localResult = await localUploadRuntime(file, plan, {
|
|
|
|
|
|
rootUri: rootUri,
|
|
|
|
|
|
documentId: documentId,
|
|
|
|
|
|
timeoutMs: 15000
|
|
|
|
|
|
});
|
|
|
|
|
|
localPayload = localResult && localResult.asset ? { asset: localResult.asset } : null;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var localForm = new FormData();
|
|
|
|
|
|
localForm.append('file', file);
|
|
|
|
|
|
localForm.append('rootUri', rootUri);
|
|
|
|
|
|
localForm.append('uploadIntent', uploadIntent);
|
|
|
|
|
|
if (documentId) localForm.append('documentId', documentId);
|
|
|
|
|
|
if (hasFolderTarget) localForm.append('targetRelativePath', String(plan.targetRelativePath || ''));
|
|
|
|
|
|
localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment');
|
|
|
|
|
|
var localResponse = await fetchWithTimeout('/api/local-folder/assets/upload', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
body: localForm
|
|
|
|
|
|
}, 15000, '本地上传');
|
|
|
|
|
|
localPayload = await localResponse.json().catch(function() { return null; });
|
|
|
|
|
|
if (!localResponse.ok || !localPayload || !localPayload.asset) {
|
|
|
|
|
|
throw new Error(localPayload && localPayload.error ? localPayload.error : '上传失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (options && options.insertIntoEditor) {
|
|
|
|
|
|
await insertUploadedAssetIntoEditor(localPayload.asset, editorRootFromUploadOptions(options));
|
|
|
|
|
|
}
|
2026-05-28 22:01:44 +08:00
|
|
|
|
if (!(options && options.insertIntoEditor)) void refreshLocalFolderSidebarSnapshot();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
window.dispatchEvent(new CustomEvent('wolai:local-assets-changed', {
|
|
|
|
|
|
detail: { docId: documentId, asset: localPayload.asset, assetIds: [localPayload.asset.id] }
|
|
|
|
|
|
}));
|
|
|
|
|
|
return localPayload.asset;
|
|
|
|
|
|
}
|
|
|
|
|
|
var mediaUploadRuntime = localUploadRuntimeFunction('uploadMediaAsset');
|
|
|
|
|
|
var payload = null;
|
|
|
|
|
|
if (mediaUploadRuntime) {
|
|
|
|
|
|
var mediaResult = await mediaUploadRuntime(file, plan, { timeoutMs: 15000 });
|
|
|
|
|
|
payload = mediaResult && mediaResult.asset ? { asset: mediaResult.asset } : null;
|
|
|
|
|
|
if (!payload || !payload.asset) {
|
|
|
|
|
|
throw new Error('上传失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-06-07 10:35:21 +08:00
|
|
|
|
document.documentElement.setAttribute('data-mnote-media-upload-retired', 'true');
|
|
|
|
|
|
throw new Error('旧 Convex Files 上传链已退役;local-first 附件请使用本地文件夹上传目标。');
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
appendUploadedAssetRow(payload.asset, plan.targetDocumentId);
|
|
|
|
|
|
if (options && options.insertIntoEditor) {
|
|
|
|
|
|
await insertUploadedAssetIntoEditor(payload.asset, editorRootFromUploadOptions(options));
|
|
|
|
|
|
}
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent('wolai:assets-changed', {
|
|
|
|
|
|
detail: { docId: plan.targetDocumentId, asset: payload.asset, assetIds: [payload.asset.id] }
|
|
|
|
|
|
}));
|
|
|
|
|
|
return payload.asset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('wolai:assets-changed', function(event) {
|
|
|
|
|
|
applyAssetsChangedToFileTree(event.detail || {});
|
|
|
|
|
|
});
|
2026-05-28 22:01:44 +08:00
|
|
|
|
window.addEventListener('wolai:local-assets-changed', function(event) {
|
|
|
|
|
|
applyAssetsChangedToFileTree(event.detail || {});
|
|
|
|
|
|
});
|
2026-06-07 01:10:31 +08:00
|
|
|
|
window.addEventListener('mnote:knowledge-rag-source-updated', function() {
|
|
|
|
|
|
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
|
|
|
|
|
});
|
2026-05-25 17:36:17 +08:00
|
|
|
|
installMindmapAssetFetchObserver();
|
|
|
|
|
|
|
|
|
|
|
|
async function uploadFilesWithResolvedTarget(files, detail, options) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('uploadFilesWithResolvedTarget');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return await runtimeFn(files, detail || {}, options || {}, {
|
|
|
|
|
|
resolveFileTreeUploadTarget: resolveFileTreeUploadTarget,
|
|
|
|
|
|
uploadFileToMediaAsset: uploadFileToMediaAsset,
|
|
|
|
|
|
alert: function(message) { window.alert(message); }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
var list = Array.from(files || []).filter(Boolean);
|
|
|
|
|
|
if (!list.length) return [];
|
|
|
|
|
|
var plan = await resolveFileTreeUploadTarget(detail || {});
|
|
|
|
|
|
var uploaded = [];
|
|
|
|
|
|
var errors = [];
|
|
|
|
|
|
for (var i = 0; i < list.length; i += 1) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
uploaded.push(await uploadFileToMediaAsset(list[i], plan, options || {}));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
errors.push(list[i].name + ': ' + (error && error.message ? error.message : '上传失败'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (errors.length) {
|
|
|
|
|
|
window.alert('部分文件上传失败:\n' + errors.slice(0, 6).join('\n') + (errors.length > 6 ? '\n...' : ''));
|
|
|
|
|
|
}
|
|
|
|
|
|
return uploaded;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openEditorUploadFilePicker(detail) {
|
|
|
|
|
|
var runtimeFn = localUploadRuntimeFunction('openEditorUploadFilePicker');
|
|
|
|
|
|
if (runtimeFn) {
|
|
|
|
|
|
return runtimeFn(detail || {}, {
|
|
|
|
|
|
rootSelector: EDITOR_UPLOAD_ROOT_SELECTOR,
|
|
|
|
|
|
editorUploadRootFromElement: editorUploadRootFromElement,
|
|
|
|
|
|
currentDocumentId: currentDocumentId,
|
|
|
|
|
|
resolveWorkspaceId: resolveWorkspaceId,
|
|
|
|
|
|
uploadFilesWithResolvedTarget: uploadFilesWithResolvedTarget
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
var uploadContext = resolveEditorUploadContext(detail || {});
|
|
|
|
|
|
var input = document.createElement('input');
|
|
|
|
|
|
input.type = 'file';
|
|
|
|
|
|
input.multiple = detail && detail.multiple !== false;
|
|
|
|
|
|
if (detail && detail.accept) input.accept = String(detail.accept);
|
|
|
|
|
|
input.style.position = 'fixed';
|
|
|
|
|
|
input.style.left = '-9999px';
|
|
|
|
|
|
input.style.top = '-9999px';
|
|
|
|
|
|
document.body.appendChild(input);
|
|
|
|
|
|
input.addEventListener('change', function() {
|
|
|
|
|
|
var files = Array.from(input.files || []);
|
|
|
|
|
|
input.remove();
|
|
|
|
|
|
void uploadFilesWithResolvedTarget(files, {
|
|
|
|
|
|
workspaceId: uploadContext.workspaceId || resolveWorkspaceId(document.body),
|
|
|
|
|
|
documentId: uploadContext.documentId || currentDocumentId(),
|
|
|
|
|
|
targetRowId: null,
|
|
|
|
|
|
uploadIntent: 'editor.markdown.attach'
|
|
|
|
|
|
}, {
|
|
|
|
|
|
insertIntoEditor: detail && detail.insertIntoEditor !== false,
|
|
|
|
|
|
editorRoot: uploadContext.root
|
|
|
|
|
|
});
|
|
|
|
|
|
}, { once: true });
|
|
|
|
|
|
input.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('mnote:editor-upload-request', function(event) {
|
|
|
|
|
|
openEditorUploadFilePicker(event.detail || {});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('tree.filetree.external-drop', function(event) {
|
|
|
|
|
|
var detail = event.detail || {};
|
|
|
|
|
|
void uploadFilesWithResolvedTarget(detail.files || [], detail, {
|
|
|
|
|
|
insertIntoEditor: String(detail.documentId || '') === currentDocumentId()
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('dragover', function(event) {
|
|
|
|
|
|
var editorTarget = closestAction(event.target, '[data-testid="mnote-leptos-tiptap-editor-stage"], .editor-surface .ProseMirror');
|
|
|
|
|
|
var hasFiles = event.dataTransfer && Array.prototype.indexOf.call(event.dataTransfer.types || [], 'Files') >= 0;
|
|
|
|
|
|
if (!editorTarget || !hasFiles) return;
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('drop', function(event) {
|
|
|
|
|
|
var editorTarget = closestAction(event.target, '[data-testid="mnote-leptos-tiptap-editor-stage"], .editor-surface .ProseMirror');
|
|
|
|
|
|
var files = event.dataTransfer ? Array.from(event.dataTransfer.files || []) : [];
|
|
|
|
|
|
if (!editorTarget || !files.length) return;
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
void uploadFilesWithResolvedTarget(files, {
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
|
|
|
|
documentId: currentDocumentId(),
|
|
|
|
|
|
targetRowId: null,
|
|
|
|
|
|
uploadIntent: 'editor.markdown.attach'
|
|
|
|
|
|
}, {
|
|
|
|
|
|
insertIntoEditor: true,
|
|
|
|
|
|
editorRoot: editorUploadRootFromElement(editorTarget)
|
|
|
|
|
|
});
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
2026-05-26 02:20:46 +08:00
|
|
|
|
const sidebarFileTreeCommandState = {
|
|
|
|
|
|
get activeTreeContextMenu() { return activeTreeContextMenu; },
|
|
|
|
|
|
set activeTreeContextMenu(value) { activeTreeContextMenu = value; },
|
|
|
|
|
|
get sidebarFileTreeClipboard() { return sidebarFileTreeClipboard; },
|
|
|
|
|
|
set sidebarFileTreeClipboard(value) { sidebarFileTreeClipboard = value; },
|
|
|
|
|
|
};
|
|
|
|
|
|
const sidebarFileTreeCommand = createSidebarFileTreeCommandRuntime({
|
|
|
|
|
|
applyRemoveDocumentDelta,
|
|
|
|
|
|
createFileTreeFolder,
|
|
|
|
|
|
createPage,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
2026-05-27 11:31:12 +08:00
|
|
|
|
currentRootUri,
|
2026-05-26 02:20:46 +08:00
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
deleteSingleFileTreeAsset,
|
|
|
|
|
|
dispatchSidebarEvent,
|
|
|
|
|
|
dispatchTreeCommand,
|
|
|
|
|
|
ensureFileTreeWritableTarget,
|
|
|
|
|
|
fileTreeRuntimeDeps,
|
|
|
|
|
|
fileTreeRuntimeFunction,
|
|
|
|
|
|
fileTreeSelectionRuntimeDeps,
|
|
|
|
|
|
fileTreeSelectionRuntimeFunction,
|
|
|
|
|
|
isFileTreePageRow,
|
|
|
|
|
|
localFilePathFromAssetId,
|
|
|
|
|
|
normalizeFileTreePageRenameTitle,
|
|
|
|
|
|
openConvexAssetFromFileTree,
|
2026-05-26 07:08:26 +08:00
|
|
|
|
openEditorAttachmentDetail: (...args) => openEditorAttachmentDetail(...args),
|
|
|
|
|
|
openEditorAttachmentDownload: (...args) => openEditorAttachmentDownload(...args),
|
|
|
|
|
|
openEditorAttachmentEditTab: (...args) => openEditorAttachmentEditTab(...args),
|
|
|
|
|
|
openEditorAttachmentNewWindow: (...args) => openEditorAttachmentNewWindow(...args),
|
2026-06-01 10:07:42 +08:00
|
|
|
|
openLocalResourceInActiveTab: (...args) => openLocalResourceInActiveTab(...args),
|
2026-05-26 02:20:46 +08:00
|
|
|
|
refreshLocalFolderSidebarSnapshot,
|
2026-05-27 11:31:12 +08:00
|
|
|
|
removeFileTreeAssetRow,
|
|
|
|
|
|
revealFileTreeResource,
|
2026-05-26 02:20:46 +08:00
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
runtimeState: sidebarFileTreeCommandState,
|
|
|
|
|
|
selectedSidebarFileTreeSelection: sidebarFileTreeSelection,
|
|
|
|
|
|
updateTitleEverywhere,
|
|
|
|
|
|
validateFileTreeRename,
|
|
|
|
|
|
});
|
|
|
|
|
|
const rowTitle = (...args) => sidebarFileTreeCommand.rowTitle(...args);
|
|
|
|
|
|
const rowCenter = (...args) => sidebarFileTreeCommand.rowCenter(...args);
|
|
|
|
|
|
const renameFileTreeAsset = (...args) => sidebarFileTreeCommand.renameFileTreeAsset(...args);
|
|
|
|
|
|
const beginFileTreeInlineRename = (...args) => sidebarFileTreeCommand.beginFileTreeInlineRename(...args);
|
|
|
|
|
|
const closeTreeContextMenu = (...args) => sidebarFileTreeCommand.closeTreeContextMenu(...args);
|
|
|
|
|
|
const copyTreeContextValue = (...args) => sidebarFileTreeCommand.copyTreeContextValue(...args);
|
|
|
|
|
|
const triggerBrowserDownload = (...args) => sidebarFileTreeCommand.triggerBrowserDownload(...args);
|
|
|
|
|
|
const recordFileTreeAction = (...args) => sidebarFileTreeCommand.recordFileTreeAction(...args);
|
|
|
|
|
|
const recordFileTreeActionStatus = (...args) => sidebarFileTreeCommand.recordFileTreeActionStatus(...args);
|
|
|
|
|
|
const documentHref = (...args) => sidebarFileTreeCommand.documentHref(...args);
|
|
|
|
|
|
const convertToPreviousSiblingChild = (...args) => sidebarFileTreeCommand.convertToPreviousSiblingChild(...args);
|
|
|
|
|
|
const fileTreeCopyPath = (...args) => sidebarFileTreeCommand.fileTreeCopyPath(...args);
|
|
|
|
|
|
const fileTreeMenuTargetParentId = (...args) => sidebarFileTreeCommand.fileTreeMenuTargetParentId(...args);
|
|
|
|
|
|
const withOfficeEditModeGuard = (...args) => sidebarFileTreeCommand.withOfficeEditModeGuard(...args);
|
|
|
|
|
|
const handleTreeContextMenuAction = (...args) => sidebarFileTreeCommand.handleTreeContextMenuAction(...args);
|
|
|
|
|
|
const appendTreeContextMenuButton = (...args) => sidebarFileTreeCommand.appendTreeContextMenuButton(...args);
|
|
|
|
|
|
const buildSidebarFileTreeContext = (...args) => sidebarFileTreeCommand.buildSidebarFileTreeContext(...args);
|
|
|
|
|
|
const evaluateSidebarFileTreeWhen = (...args) => sidebarFileTreeCommand.evaluateSidebarFileTreeWhen(...args);
|
|
|
|
|
|
const openTreeContextMenu = (...args) => sidebarFileTreeCommand.openTreeContextMenu(...args);
|
|
|
|
|
|
const openPageTreeContextMenu = (...args) => sidebarFileTreeCommand.openPageTreeContextMenu(...args);
|
|
|
|
|
|
const openFileTreeContextMenu = (...args) => sidebarFileTreeCommand.openFileTreeContextMenu(...args);
|
|
|
|
|
|
const visibleFileTreeRows = (...args) => sidebarFileTreeCommand.visibleFileTreeRows(...args);
|
|
|
|
|
|
const syncSidebarFileTreeSelection = (...args) => sidebarFileTreeCommand.syncSidebarFileTreeSelection(...args);
|
|
|
|
|
|
const selectSidebarFileTreeRow = (...args) => sidebarFileTreeCommand.selectSidebarFileTreeRow(...args);
|
|
|
|
|
|
const selectSidebarFileTreeDocument = (...args) => sidebarFileTreeCommand.selectSidebarFileTreeDocument(...args);
|
|
|
|
|
|
const selectSidebarFileTreeRowById = (...args) => sidebarFileTreeCommand.selectSidebarFileTreeRowById(...args);
|
|
|
|
|
|
const pendingLocalFolderRestoreRowId = (...args) => sidebarFileTreeCommand.pendingLocalFolderRestoreRowId(...args);
|
|
|
|
|
|
const clearPendingLocalFolderRestoreRowId = (...args) => sidebarFileTreeCommand.clearPendingLocalFolderRestoreRowId(...args);
|
|
|
|
|
|
const applyPendingLocalFolderRestoreFocusOnce = (...args) => sidebarFileTreeCommand.applyPendingLocalFolderRestoreFocusOnce(...args);
|
|
|
|
|
|
const schedulePendingLocalFolderRestoreFocus = (...args) => sidebarFileTreeCommand.schedulePendingLocalFolderRestoreFocus(...args);
|
|
|
|
|
|
const selectedSidebarFileTreeRowIdsForDrag = (...args) => sidebarFileTreeCommand.selectedSidebarFileTreeRowIdsForDrag(...args);
|
|
|
|
|
|
const selectedSidebarFileTreeRows = (...args) => sidebarFileTreeCommand.selectedSidebarFileTreeRows(...args);
|
|
|
|
|
|
const fileTreeRowDocumentId = (...args) => sidebarFileTreeCommand.fileTreeRowDocumentId(...args);
|
|
|
|
|
|
const fileTreeRowAssetId = (...args) => sidebarFileTreeCommand.fileTreeRowAssetId(...args);
|
|
|
|
|
|
const decodeLocalEncodedPath = (...args) => sidebarFileTreeCommand.decodeLocalEncodedPath(...args);
|
|
|
|
|
|
const fileTreeRowLocalRelativePath = (...args) => sidebarFileTreeCommand.fileTreeRowLocalRelativePath(...args);
|
|
|
|
|
|
const fileTreeRowLocalUploadTargetRelativePath = (...args) => sidebarFileTreeCommand.fileTreeRowLocalUploadTargetRelativePath(...args);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
const readWorkspacePathFromRow = (...args) => sidebarFileTreeCommand.readWorkspacePathFromRow(...args);
|
2026-05-26 02:20:46 +08:00
|
|
|
|
const fileTreeRowKind = (...args) => sidebarFileTreeCommand.fileTreeRowKind(...args);
|
|
|
|
|
|
const isFileTreeDownloadableAssetRow = (...args) => sidebarFileTreeCommand.isFileTreeDownloadableAssetRow(...args);
|
|
|
|
|
|
const isFileTreeDownloadableRow = (...args) => sidebarFileTreeCommand.isFileTreeDownloadableRow(...args);
|
|
|
|
|
|
const fileTreeAssetDownloadDetail = (...args) => sidebarFileTreeCommand.fileTreeAssetDownloadDetail(...args);
|
|
|
|
|
|
const selectedSidebarFileTreeRowsForDownload = (...args) => sidebarFileTreeCommand.selectedSidebarFileTreeRowsForDownload(...args);
|
|
|
|
|
|
const selectedSidebarFileTreeAssetRowsForDownload = (...args) => sidebarFileTreeCommand.selectedSidebarFileTreeAssetRowsForDownload(...args);
|
|
|
|
|
|
const downloadSelectedFileTreeAssetRows = (...args) => sidebarFileTreeCommand.downloadSelectedFileTreeAssetRows(...args);
|
|
|
|
|
|
const hasSelectedDocumentAncestor = (...args) => sidebarFileTreeCommand.hasSelectedDocumentAncestor(...args);
|
|
|
|
|
|
const classifySidebarFileTreeAsset = (...args) => sidebarFileTreeCommand.classifySidebarFileTreeAsset(...args);
|
|
|
|
|
|
const buildSidebarFileTreeDeletePlan = (...args) => sidebarFileTreeCommand.buildSidebarFileTreeDeletePlan(...args);
|
|
|
|
|
|
const sidebarFileTreeDeleteConfirmText = (...args) => sidebarFileTreeCommand.sidebarFileTreeDeleteConfirmText(...args);
|
|
|
|
|
|
const postSidebarFileTreeJson = (...args) => sidebarFileTreeCommand.postSidebarFileTreeJson(...args);
|
|
|
|
|
|
const fileTreeRowsByRowIds = (...args) => sidebarFileTreeCommand.fileTreeRowsByRowIds(...args);
|
|
|
|
|
|
const fileTreeChildCount = (...args) => sidebarFileTreeCommand.fileTreeChildCount(...args);
|
2026-05-27 11:31:12 +08:00
|
|
|
|
const moveSidebarFileTreeRows = (...args) => sidebarFileTreeCommand.moveSidebarFileTreeRows(...args);
|
2026-05-26 02:20:46 +08:00
|
|
|
|
const pasteSidebarFileTreeClipboard = (...args) => sidebarFileTreeCommand.pasteSidebarFileTreeClipboard(...args);
|
|
|
|
|
|
const deleteSelectedSidebarFileTreeRows = (...args) => sidebarFileTreeCommand.deleteSelectedSidebarFileTreeRows(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
2026-05-26 02:27:08 +08:00
|
|
|
|
sidebarFileTreeUpload = createSidebarFileTreeUploadRuntime({
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
fileTreeRuntimeDeps,
|
|
|
|
|
|
fileTreeRuntimeFunction,
|
|
|
|
|
|
recordFileTreeAction,
|
|
|
|
|
|
recordFileTreeActionStatus,
|
|
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
rowTitle,
|
|
|
|
|
|
selectedSidebarFileTreeSelection: sidebarFileTreeSelection,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function ensureSearchModal() {
|
|
|
|
|
|
var existing = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
if (existing instanceof HTMLElement) return existing;
|
|
|
|
|
|
|
|
|
|
|
|
var overlay = document.createElement('div');
|
|
|
|
|
|
overlay.className = 'wolai-search-overlay';
|
|
|
|
|
|
overlay.setAttribute('data-testid', 'wolai-search-modal');
|
|
|
|
|
|
overlay.setAttribute('role', 'dialog');
|
|
|
|
|
|
overlay.setAttribute('aria-modal', 'true');
|
|
|
|
|
|
overlay.hidden = true;
|
|
|
|
|
|
|
|
|
|
|
|
overlay.innerHTML = '' +
|
|
|
|
|
|
'<div class="wolai-search-dialog">' +
|
|
|
|
|
|
'<div class="wolai-search-input-row">' +
|
|
|
|
|
|
'<span class="material-symbols-outlined wolai-search-input-icon" data-icon="search" aria-hidden="true"></span>' +
|
|
|
|
|
|
'<input data-testid="wolai-search-input" class="wolai-search-input" type="search" autocomplete="off" placeholder="在当前工作区中搜索" />' +
|
|
|
|
|
|
'<button type="button" class="wolai-search-close" data-testid="wolai-search-close" aria-label="关闭搜索">×</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-search-options" data-testid="wolai-search-options" aria-label="搜索选项" hidden>' +
|
|
|
|
|
|
'<div class="wolai-search-options-left">' +
|
|
|
|
|
|
'<span class="wolai-search-switch-control"><span>仅匹配标题</span><button type="button" class="wolai-search-switch" data-search-switch="title" role="switch" aria-checked="false" aria-label="仅匹配标题"></button></span>' +
|
|
|
|
|
|
'<span class="wolai-search-switch-control"><span>精确匹配</span><button type="button" class="wolai-search-switch" data-search-switch="exact" role="switch" aria-checked="false" aria-label="精确匹配"></button></span>' +
|
2026-06-09 09:20:56 +08:00
|
|
|
|
'<span class="wolai-search-sort-control"><span>资料库模式</span><select class="wolai-search-sort-value" data-search-knowledge-mode aria-label="资料库检索模式"><option value="mix">综合</option><option value="hybrid">图谱混合</option><option value="naive">向量</option><option value="local">实体</option><option value="global">关系</option><option value="exact">关键词</option></select></span>' +
|
2026-05-25 17:36:17 +08:00
|
|
|
|
'<span class="wolai-search-sort-control"><span>按编辑时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="updated" aria-label="按编辑时间范围">所有</button></span>' +
|
|
|
|
|
|
'<span class="wolai-search-sort-control"><span>按创建时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="created" aria-label="按创建时间范围">所有</button></span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-search-options-right">' +
|
2026-06-08 20:35:49 +08:00
|
|
|
|
'<span class="wolai-search-switch-control"><span>全盘资料库</span><button type="button" class="wolai-search-switch" data-search-switch="knowledge" role="switch" aria-checked="false" aria-label="全盘资料库检索"></button></span>' +
|
2026-05-25 17:36:17 +08:00
|
|
|
|
'<span class="wolai-search-switch-control"><span>页面内搜索</span><button type="button" class="wolai-search-switch" data-search-switch="page" role="switch" aria-checked="false" aria-label="页面内搜索"></button></span>' +
|
2026-06-08 20:35:49 +08:00
|
|
|
|
'<span class="wolai-search-switch-control"><span>折叠同来源</span><button type="button" class="wolai-search-switch is-on" data-search-switch="collapseSource" role="switch" aria-checked="true" aria-label="默认折叠相同来源结果"></button></span>' +
|
2026-05-25 17:36:17 +08:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-search-result-meta" data-testid="wolai-search-result-meta"></div>' +
|
|
|
|
|
|
'<div class="wolai-search-results" data-testid="wolai-search-results" data-search-results-owner="rust-kernel"></div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
var closeButton = overlay.querySelector('[data-testid="wolai-search-close"]');
|
2026-06-08 20:35:49 +08:00
|
|
|
|
applySearchSwitchState(overlay, { collapseSource: readSearchCollapseSourcesDefault() });
|
|
|
|
|
|
if (input) input.addEventListener('input', function() {
|
|
|
|
|
|
searchUiState.hasRendered = false;
|
|
|
|
|
|
scheduleSearchResultsRender();
|
|
|
|
|
|
});
|
2026-05-25 17:36:17 +08:00
|
|
|
|
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
|
|
|
|
|
button.addEventListener('click', function() {
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var switchName = searchText(button.getAttribute('data-search-switch'));
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var isOn = button.getAttribute('aria-checked') !== 'true';
|
|
|
|
|
|
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
|
|
|
|
|
button.classList.toggle('is-on', isOn);
|
2026-06-08 20:35:49 +08:00
|
|
|
|
if (switchName === 'collapseSource') {
|
|
|
|
|
|
writeSearchCollapseSourcesDefault(isOn);
|
|
|
|
|
|
searchUiState.sourceCollapsed = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
searchUiState.hasRendered = false;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
scheduleSearchResultsRender();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var knowledgeModeSelect = overlay.querySelector('[data-search-knowledge-mode]');
|
|
|
|
|
|
if (knowledgeModeSelect) {
|
|
|
|
|
|
knowledgeModeSelect.addEventListener('change', function() {
|
|
|
|
|
|
searchUiState.hasRendered = false;
|
|
|
|
|
|
scheduleSearchResultsRender();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (closeButton) closeButton.addEventListener('click', closeSearchModal);
|
|
|
|
|
|
overlay.addEventListener('click', function(event) {
|
|
|
|
|
|
if (event.target === overlay) closeSearchModal();
|
|
|
|
|
|
});
|
|
|
|
|
|
return overlay;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var activeSearchRequestId = 0;
|
|
|
|
|
|
var searchRenderTimer = 0;
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var SEARCH_COLLAPSE_SOURCES_KEY = 'mnote.search.collapseSourcesDefault.v1';
|
|
|
|
|
|
var searchUiState = {
|
|
|
|
|
|
query: '',
|
|
|
|
|
|
switches: {},
|
|
|
|
|
|
metaHtml: '',
|
|
|
|
|
|
resultsHtml: '',
|
|
|
|
|
|
items: [],
|
2026-06-09 09:20:56 +08:00
|
|
|
|
knowledgeMode: 'mix',
|
2026-06-08 20:35:49 +08:00
|
|
|
|
sourceCollapsed: {},
|
|
|
|
|
|
scrollTop: 0,
|
|
|
|
|
|
signature: '',
|
|
|
|
|
|
hasRendered: false
|
|
|
|
|
|
};
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
function searchText(value) {
|
|
|
|
|
|
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
function searchNonWhitespaceCharCount(value) {
|
|
|
|
|
|
return String(value == null ? '' : value).replace(/\s+/g, '').length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function currentWorkspaceName() {
|
|
|
|
|
|
var name = document.querySelector('.sidebar-workspace-name');
|
|
|
|
|
|
return searchText(name && name.textContent) || '当前工作区';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentDocumentId() {
|
|
|
|
|
|
var shell = document.querySelector('.document-shell[data-document-id]');
|
|
|
|
|
|
var bodyId = document.body && document.body.getAttribute('data-document-id');
|
|
|
|
|
|
return searchText(shell && shell.getAttribute('data-document-id')) || searchText(bodyId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 23:00:53 +08:00
|
|
|
|
function localResourceDocumentIdFromPath(path) {
|
|
|
|
|
|
var normalized = searchText(path).replace(/\\/g, '/');
|
|
|
|
|
|
return normalized ? 'local-resource:' + normalized.replace(/\//g, '~2F') : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentSearchDocumentId() {
|
|
|
|
|
|
var activePanel = document.querySelector('[data-mnote-resource-tab-panel][data-pane-role="primary"]:not([hidden])');
|
|
|
|
|
|
if (activePanel instanceof HTMLElement) {
|
|
|
|
|
|
var resourceKind = searchText(activePanel.getAttribute('data-resource-kind')).toLowerCase();
|
|
|
|
|
|
var resourcePath = searchText(activePanel.getAttribute('data-resource-path'));
|
|
|
|
|
|
if (resourceKind && resourceKind !== 'markdown' && resourcePath) return localResourceDocumentIdFromPath(resourcePath);
|
|
|
|
|
|
try {
|
|
|
|
|
|
var locator = JSON.parse(activePanel.getAttribute('data-mnote-evidence-locator') || 'null');
|
|
|
|
|
|
var locatorDoc = searchText(locator && (locator.ownerDocumentId || locator.owner_document_id));
|
|
|
|
|
|
if (locatorDoc) return locatorDoc;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
return currentDocumentId();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function searchSwitchValue(overlay, name) {
|
|
|
|
|
|
var button = overlay.querySelector('[data-search-switch="' + name + '"]');
|
|
|
|
|
|
return button instanceof HTMLElement && button.getAttribute('aria-checked') === 'true';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
function searchKnowledgeModeValue(overlay) {
|
|
|
|
|
|
var select = overlay.querySelector('[data-search-knowledge-mode]');
|
|
|
|
|
|
var value = select && 'value' in select ? searchText(select.value).toLowerCase() : '';
|
|
|
|
|
|
if (['mix', 'hybrid', 'naive', 'local', 'global', 'exact'].indexOf(value) >= 0) return value;
|
|
|
|
|
|
return 'mix';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function knowledgeSearchRequestMode(overlay) {
|
|
|
|
|
|
if (searchSwitchValue(overlay, 'exact')) return 'exact';
|
|
|
|
|
|
return searchKnowledgeModeValue(overlay);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function knowledgeSearchModeLabel(mode) {
|
|
|
|
|
|
if (mode === 'exact') return '关键词';
|
|
|
|
|
|
if (mode === 'naive') return '向量';
|
|
|
|
|
|
if (mode === 'local') return '实体';
|
|
|
|
|
|
if (mode === 'global') return '关系';
|
|
|
|
|
|
if (mode === 'hybrid') return '图谱混合';
|
|
|
|
|
|
return '综合';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
function readSearchCollapseSourcesDefault() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var stored = window.localStorage && window.localStorage.getItem(SEARCH_COLLAPSE_SOURCES_KEY);
|
|
|
|
|
|
if (stored === '0') return false;
|
|
|
|
|
|
if (stored === '1') return true;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function writeSearchCollapseSourcesDefault(value) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (window.localStorage) window.localStorage.setItem(SEARCH_COLLAPSE_SOURCES_KEY, value ? '1' : '0');
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function collectSearchSwitchState(overlay) {
|
|
|
|
|
|
var state = {};
|
|
|
|
|
|
if (!(overlay instanceof HTMLElement)) return state;
|
|
|
|
|
|
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
|
|
|
|
|
if (!(button instanceof HTMLElement)) return;
|
|
|
|
|
|
var name = searchText(button.getAttribute('data-search-switch'));
|
|
|
|
|
|
if (!name) return;
|
|
|
|
|
|
state[name] = button.getAttribute('aria-checked') === 'true';
|
|
|
|
|
|
});
|
|
|
|
|
|
return state;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applySearchSwitchState(overlay, switches) {
|
|
|
|
|
|
if (!(overlay instanceof HTMLElement) || !switches || typeof switches !== 'object') return;
|
|
|
|
|
|
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
|
|
|
|
|
if (!(button instanceof HTMLElement)) return;
|
|
|
|
|
|
var name = searchText(button.getAttribute('data-search-switch'));
|
|
|
|
|
|
if (!name || !Object.prototype.hasOwnProperty.call(switches, name)) return;
|
|
|
|
|
|
var isOn = Boolean(switches[name]);
|
|
|
|
|
|
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
|
|
|
|
|
button.classList.toggle('is-on', isOn);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function searchRequestSignature(overlay, query) {
|
|
|
|
|
|
var switches = collectSearchSwitchState(overlay);
|
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
|
query: searchText(query),
|
|
|
|
|
|
knowledge: Boolean(switches.knowledge),
|
|
|
|
|
|
page: Boolean(switches.page),
|
|
|
|
|
|
title: Boolean(switches.title),
|
|
|
|
|
|
exact: Boolean(switches.exact),
|
|
|
|
|
|
collapseSource: Boolean(switches.collapseSource),
|
2026-06-09 09:20:56 +08:00
|
|
|
|
knowledgeMode: searchKnowledgeModeValue(overlay),
|
2026-06-08 20:35:49 +08:00
|
|
|
|
workspaceId: resolveWorkspaceId(document.body) || '',
|
|
|
|
|
|
rootUri: currentRootUri() || ''
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveSearchUiState(overlay) {
|
|
|
|
|
|
if (!(overlay instanceof HTMLElement)) return;
|
|
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
|
|
|
|
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
|
|
|
|
|
var query = input && 'value' in input ? searchText(input.value) : searchUiState.query;
|
|
|
|
|
|
searchUiState.query = query;
|
|
|
|
|
|
searchUiState.switches = collectSearchSwitchState(overlay);
|
2026-06-09 09:20:56 +08:00
|
|
|
|
searchUiState.knowledgeMode = searchKnowledgeModeValue(overlay);
|
2026-06-08 20:35:49 +08:00
|
|
|
|
searchUiState.metaHtml = meta ? meta.innerHTML : searchUiState.metaHtml;
|
|
|
|
|
|
searchUiState.resultsHtml = results ? results.innerHTML : searchUiState.resultsHtml;
|
|
|
|
|
|
searchUiState.items = Array.isArray(window.__mnoteSearchResults) ? window.__mnoteSearchResults.slice() : searchUiState.items;
|
|
|
|
|
|
searchUiState.scrollTop = results instanceof HTMLElement ? results.scrollTop : searchUiState.scrollTop;
|
|
|
|
|
|
searchUiState.signature = query ? searchRequestSignature(overlay, query) : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function restoreSearchUiState(overlay) {
|
|
|
|
|
|
if (!(overlay instanceof HTMLElement) || !searchUiState.hasRendered) return false;
|
|
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
|
|
|
|
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
|
|
|
|
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
|
|
|
|
|
if (input && 'value' in input) input.value = searchUiState.query || '';
|
|
|
|
|
|
applySearchSwitchState(overlay, searchUiState.switches);
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var knowledgeModeSelect = overlay.querySelector('[data-search-knowledge-mode]');
|
|
|
|
|
|
if (knowledgeModeSelect && 'value' in knowledgeModeSelect) knowledgeModeSelect.value = searchUiState.knowledgeMode || 'mix';
|
2026-06-08 20:35:49 +08:00
|
|
|
|
if (options instanceof HTMLElement) options.hidden = !searchText(searchUiState.query);
|
|
|
|
|
|
if (meta && searchUiState.metaHtml) meta.innerHTML = searchUiState.metaHtml;
|
|
|
|
|
|
if (results && searchUiState.resultsHtml) {
|
|
|
|
|
|
results.innerHTML = searchUiState.resultsHtml;
|
|
|
|
|
|
results.scrollTop = Number(searchUiState.scrollTop || 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
window.__mnoteSearchResults = Array.isArray(searchUiState.items) ? searchUiState.items.slice() : [];
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 00:33:02 +08:00
|
|
|
|
function searchHighlightTerms(item, query) {
|
|
|
|
|
|
var info = item && item.matchInfo || item && item.evidence && item.evidence.matchInfo || null;
|
|
|
|
|
|
var terms = info && Array.isArray(info.matchedTerms) ? info.matchedTerms : [];
|
|
|
|
|
|
terms = terms.map(searchText).filter(Boolean);
|
|
|
|
|
|
if (!terms.length) {
|
|
|
|
|
|
terms = searchText(query).split(/[\s,;:,;:。、()[\]{}]+/).map(searchText).filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
terms.sort(function(left, right) { return right.length - left.length; });
|
|
|
|
|
|
return terms.filter(function(term, index) { return terms.indexOf(term) === index; });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function highlightSearchText(value, query, exact, terms) {
|
2026-06-05 23:00:53 +08:00
|
|
|
|
var text = searchText(value);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var cleanQuery = searchText(query);
|
2026-06-05 23:00:53 +08:00
|
|
|
|
if (!text || !cleanQuery) return escapeHtml(text);
|
|
|
|
|
|
var lower = text.toLowerCase();
|
|
|
|
|
|
var lowerQuery = cleanQuery.toLowerCase();
|
|
|
|
|
|
var exactIndex = lower.indexOf(lowerQuery);
|
|
|
|
|
|
if (exact || exactIndex >= 0) {
|
|
|
|
|
|
if (exactIndex < 0) return escapeHtml(text);
|
|
|
|
|
|
return escapeHtml(text.slice(0, exactIndex)) +
|
|
|
|
|
|
'<mark>' + escapeHtml(text.slice(exactIndex, exactIndex + cleanQuery.length)) + '</mark>' +
|
|
|
|
|
|
escapeHtml(text.slice(exactIndex + cleanQuery.length));
|
|
|
|
|
|
}
|
2026-06-06 00:33:02 +08:00
|
|
|
|
terms = Array.isArray(terms) ? terms.map(searchText).filter(Boolean) : [];
|
|
|
|
|
|
if (!terms.length) return escapeHtml(text);
|
|
|
|
|
|
var ranges = [];
|
|
|
|
|
|
terms.forEach(function(term) {
|
|
|
|
|
|
var lowerTerm = term.toLowerCase();
|
|
|
|
|
|
var start = lower.indexOf(lowerTerm);
|
|
|
|
|
|
while (start >= 0) {
|
|
|
|
|
|
var end = start + lowerTerm.length;
|
|
|
|
|
|
var overlaps = ranges.some(function(range) { return start < range.end && end > range.start; });
|
|
|
|
|
|
if (!overlaps) ranges.push({ start: start, end: end });
|
|
|
|
|
|
start = lower.indexOf(lowerTerm, end);
|
2026-06-05 23:00:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-06-06 00:33:02 +08:00
|
|
|
|
if (!ranges.length) return escapeHtml(text);
|
|
|
|
|
|
ranges.sort(function(left, right) { return left.start - right.start; });
|
|
|
|
|
|
var html = '';
|
|
|
|
|
|
var cursor = 0;
|
|
|
|
|
|
ranges.forEach(function(range) {
|
|
|
|
|
|
if (range.start > cursor) html += escapeHtml(text.slice(cursor, range.start));
|
|
|
|
|
|
html += '<mark>' + escapeHtml(text.slice(range.start, range.end)) + '</mark>';
|
|
|
|
|
|
cursor = range.end;
|
|
|
|
|
|
});
|
|
|
|
|
|
if (cursor < text.length) html += escapeHtml(text.slice(cursor));
|
|
|
|
|
|
return html;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
function cleanSearchDisplayText(value) {
|
|
|
|
|
|
var text = String(value == null ? '' : value);
|
|
|
|
|
|
text = text.replace(/<drawing\b[^>]*\/?>/gi, ' ');
|
|
|
|
|
|
text = text.replace(/<equation\b[^>]*>([\s\S]*?)<\/equation>/gi, '$1');
|
|
|
|
|
|
text = text.replace(/<drawing\b[^<>]*$/gi, ' ');
|
|
|
|
|
|
text = text.replace(/<\/?equat[^<>]*$/gi, ' ');
|
|
|
|
|
|
text = text.replace(/<\/?e(?:q(?:uation)?)?[^<>]*$/gi, ' ');
|
|
|
|
|
|
text = text.replace(/<[^>]+>/g, ' ');
|
|
|
|
|
|
text = text
|
|
|
|
|
|
.replace(/\b(?:equation|latex|drawing)\b(?:\s+[a-z_-]+=(?:"[^"]*"|'[^']*'|[^\s<>]+))*/gi, ' ')
|
|
|
|
|
|
.replace(/\\left|\\right|\\mathrm|\\text|\\operatorname/g, '')
|
|
|
|
|
|
.replace(/\\gt/g, '>')
|
|
|
|
|
|
.replace(/\\lt/g, '<')
|
|
|
|
|
|
.replace(/\\sim/g, '∼')
|
|
|
|
|
|
.replace(/\\[a-zA-Z]+/g, ' ')
|
|
|
|
|
|
.replace(/\{([^{}]*)\}/g, '$1')
|
|
|
|
|
|
.replace(/\{([^{}]*)\}/g, '$1')
|
|
|
|
|
|
.replace(/[_^]/g, '')
|
|
|
|
|
|
.replace(/[<>]\/?(?:equation|eq)\b[^<>]*>?/gi, ' ')
|
|
|
|
|
|
.replace(/<\/?e(?:q(?:uation)?)?[^<>\s]*>?/gi, ' ')
|
|
|
|
|
|
.replace(/\s+(?:equation|latex|drawing)\s+/gi, ' ');
|
|
|
|
|
|
return searchText(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
function searchResultEvidenceLocator(item) {
|
2026-06-08 20:35:49 +08:00
|
|
|
|
if (item && item.locator && typeof item.locator === 'object') return item.locator;
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
function searchResultSourceKey(item) {
|
|
|
|
|
|
var locator = searchResultEvidenceLocator(item);
|
|
|
|
|
|
var locatorPath = locator ? searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path) : '';
|
|
|
|
|
|
return locatorPath || searchText(item && (item.path || item.documentId || item.nodeId || item.id)) || 'unknown-source';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function searchResultSourceTitle(item) {
|
|
|
|
|
|
var key = searchResultSourceKey(item);
|
|
|
|
|
|
var locator = searchResultEvidenceLocator(item);
|
|
|
|
|
|
var path = locator ? searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path) : '';
|
|
|
|
|
|
var sourcePath = path || searchText(item && item.path) || key;
|
|
|
|
|
|
var title = searchText(item && (item.title || item.name || item.documentTitle));
|
|
|
|
|
|
return title || sourcePath.split('/').filter(Boolean).pop() || sourcePath || '未知来源';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderSearchResultButton(item, index, query, exact, grouped) {
|
|
|
|
|
|
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var snippet = searchText(item.displayQuote || item.snippet || item.evidence && item.evidence.displayQuote || item.evidence && item.evidence.quote || (Array.isArray(item.evidence) && item.evidence[0] && (item.evidence[0].displayQuote || item.evidence[0].quote || item.evidence[0].snippet)) || '');
|
|
|
|
|
|
if (!item.displayQuote) snippet = cleanSearchDisplayText(snippet);
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
|
|
|
|
|
var resourceType = searchText(item.resourceType || item.resourceKind || 'page');
|
|
|
|
|
|
var locator = searchResultEvidenceLocator(item);
|
|
|
|
|
|
var locatorAttr = locator ? escapeHtml(JSON.stringify(locator)) : '';
|
|
|
|
|
|
var highlightTerms = searchHighlightTerms(item, query);
|
|
|
|
|
|
var titleHtml = grouped && snippet ? '' : '<span class="wolai-search-result-title">' + highlightSearchText(title, query, exact, highlightTerms) + '</span>';
|
|
|
|
|
|
var page = locator && locator.page != null && locator.page !== '' ? String(locator.page) : '';
|
|
|
|
|
|
var metaParts = grouped ? [] : [escapeHtml(path)];
|
|
|
|
|
|
if (page) metaParts.push('第 ' + escapeHtml(page) + ' 页');
|
|
|
|
|
|
if (!grouped) metaParts.push('<span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span>');
|
|
|
|
|
|
var metaHtml = metaParts.length ? '<span class="wolai-search-result-path"><span>' + metaParts.join('</span><span>') + '</span></span>' : '';
|
|
|
|
|
|
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="' + escapeHtml(item.provider || '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">' + titleHtml +
|
|
|
|
|
|
'<span class="wolai-search-result-snippet">' + (snippet ? highlightSearchText(snippet, query, exact, highlightTerms) : escapeHtml(path)) + '</span>' +
|
|
|
|
|
|
metaHtml + '</span>' +
|
|
|
|
|
|
'</button>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderSearchResultsHtml(items, overlay, query) {
|
|
|
|
|
|
var exact = searchSwitchValue(overlay, 'exact');
|
|
|
|
|
|
if (!searchSwitchValue(overlay, 'collapseSource')) {
|
|
|
|
|
|
return items.map(function(item, index) {
|
|
|
|
|
|
return renderSearchResultButton(item, index, query, exact, false);
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
var groups = [];
|
|
|
|
|
|
var groupByKey = {};
|
|
|
|
|
|
items.forEach(function(item, index) {
|
|
|
|
|
|
var key = searchResultSourceKey(item);
|
|
|
|
|
|
var group = groupByKey[key];
|
|
|
|
|
|
if (!group) {
|
|
|
|
|
|
group = { key: key, title: searchResultSourceTitle(item), path: searchText(item.path || key), resourceType: searchText(item.resourceType || item.resourceKind || 'page'), rows: [] };
|
|
|
|
|
|
groupByKey[key] = group;
|
|
|
|
|
|
groups.push(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
group.rows.push({ item: item, index: index });
|
|
|
|
|
|
});
|
|
|
|
|
|
return groups.map(function(group) {
|
|
|
|
|
|
var collapsed = Object.prototype.hasOwnProperty.call(searchUiState.sourceCollapsed, group.key)
|
|
|
|
|
|
? searchUiState.sourceCollapsed[group.key]
|
|
|
|
|
|
: true;
|
|
|
|
|
|
var children = group.rows.map(function(row) {
|
|
|
|
|
|
return renderSearchResultButton(row.item, row.index, query, exact, true);
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
return '<section class="wolai-search-source-group" data-search-source-key="' + escapeHtml(group.key) + '">' +
|
|
|
|
|
|
'<button type="button" class="wolai-search-source-header" data-search-source-toggle="true" aria-expanded="' + (collapsed ? 'false' : 'true') + '">' +
|
|
|
|
|
|
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
|
|
|
|
|
'<span class="wolai-search-source-main"><span class="wolai-search-result-title">' + escapeHtml(group.title) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-search-result-path"><span class="wolai-search-result-type">' + escapeHtml(group.resourceType) + '</span><span>' + String(group.rows.length) + ' 处</span></span></span>' +
|
|
|
|
|
|
'<span class="wolai-search-source-chevron" aria-hidden="true">' + (collapsed ? '展开' : '收起') + '</span>' +
|
|
|
|
|
|
'</button>' +
|
|
|
|
|
|
'<div class="wolai-search-source-results" data-search-source-results="true"' + (collapsed ? ' hidden' : '') + '>' + children + '</div>' +
|
|
|
|
|
|
'</section>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 23:00:53 +08:00
|
|
|
|
function evidenceLocatorLineRange(locator) {
|
|
|
|
|
|
return locator && (locator.lineRange || locator.line_range) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function evidenceLocatorCharRange(locator) {
|
|
|
|
|
|
return locator && (locator.charRange || locator.char_range) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function evidenceLocatorBlockId(locator) {
|
|
|
|
|
|
return searchText(locator && (locator.blockId || locator.block_id));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resourceHrefForEvidenceLocator(resourceKind, resourcePath, fileName, ownerDocumentId, locator) {
|
|
|
|
|
|
var localFileUrl = buildLocalFileOpenUrl(resourcePath, false);
|
|
|
|
|
|
if (!localFileUrl) return '';
|
|
|
|
|
|
if (resourceKind === 'pdf') return buildPdfPreviewOpenUrl(localFileUrl, fileName);
|
|
|
|
|
|
if (resourceKind === 'office') {
|
|
|
|
|
|
var fileType = inferOnlyOfficeFileType(fileName, '') || (fileName.indexOf('.') >= 0 ? fileName.split('.').pop() : 'docx');
|
|
|
|
|
|
return buildOfficePreviewOpenUrl({
|
|
|
|
|
|
fileUrl: localFileUrl,
|
|
|
|
|
|
fileName: fileName,
|
|
|
|
|
|
fileType: fileType,
|
|
|
|
|
|
assetId: 'local-file:' + resourcePath,
|
|
|
|
|
|
documentId: ownerDocumentId || currentDocumentId() || '',
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(document.body) || '',
|
|
|
|
|
|
sourceKind: currentSourceKind() || 'local_folder',
|
|
|
|
|
|
rootUri: searchText(locator && (locator.rootUri || locator.root_uri) || currentRootUri())
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return localFileUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
async function openEvidenceSearchResult(item, event) {
|
|
|
|
|
|
var locator = searchResultEvidenceLocator(item);
|
|
|
|
|
|
if (!locator) {
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var citationUrl = searchText(item && (item.citationUrl || item.publicPath));
|
|
|
|
|
|
if (citationUrl) {
|
|
|
|
|
|
closeSearchModal();
|
|
|
|
|
|
window.location.assign(citationUrl);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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);
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
var searchInput = overlay instanceof HTMLElement ? overlay.querySelector('[data-testid="wolai-search-input"]') : null;
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var searchQueryText = searchText(item && (item.searchQuery || item.query) || locator && locator.openAction && locator.openAction.params && locator.openAction.params.searchQuery)
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|| searchText(searchInput && 'value' in searchInput ? searchInput.value : '')
|
|
|
|
|
|
|| searchText(searchUiState.query);
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var locatorEvidenceText = searchText(item && item.locatorEvidenceText || locator.evidenceText || locator.query || locator.openAction && locator.openAction.params && (locator.openAction.params.evidenceText || locator.openAction.params.query) || '');
|
|
|
|
|
|
var locatorParams = locator && locator.openAction && locator.openAction.params && typeof locator.openAction.params === 'object' ? locator.openAction.params : {};
|
2026-06-04 18:51:16 +08:00
|
|
|
|
var openTarget = event && event.altKey ? 'side' : 'active-tab';
|
2026-06-05 23:00:53 +08:00
|
|
|
|
if (resourcePath && resourceKind) {
|
2026-06-04 18:51:16 +08:00
|
|
|
|
var fileName = resourcePath.split('/').filter(Boolean).pop() || resourcePath;
|
2026-06-05 23:00:53 +08:00
|
|
|
|
var normalizedKind = resourceKind === 'raw_file' || resourceKind === 'resource' ? fileTreeIconKindForFileName(fileName) || 'file' : resourceKind;
|
|
|
|
|
|
var hostDocumentId = currentDocumentId() || ownerDocumentId || '';
|
|
|
|
|
|
var href = resourceHrefForEvidenceLocator(normalizedKind, resourcePath, fileName, hostDocumentId, locator);
|
|
|
|
|
|
var openedInResourceTab = await openLocalResourceInActiveTab({
|
2026-06-04 18:51:16 +08:00
|
|
|
|
path: resourcePath,
|
|
|
|
|
|
title: fileName,
|
2026-06-05 23:00:53 +08:00
|
|
|
|
kind: normalizedKind,
|
2026-06-04 18:51:16 +08:00
|
|
|
|
href: href,
|
2026-06-05 23:00:53 +08:00
|
|
|
|
officeUrl: normalizedKind === 'office' ? href : '',
|
2026-06-04 18:51:16 +08:00
|
|
|
|
assetId: 'local-file:' + resourcePath,
|
2026-06-05 23:00:53 +08:00
|
|
|
|
documentId: hostDocumentId,
|
|
|
|
|
|
ownerDocumentId: ownerDocumentId || hostDocumentId,
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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),
|
2026-06-05 23:00:53 +08:00
|
|
|
|
blockId: evidenceLocatorBlockId(locator),
|
2026-06-09 09:20:56 +08:00
|
|
|
|
paragraphOrdinal: searchText(locator.paragraphOrdinal || locatorParams.paragraphOrdinal),
|
|
|
|
|
|
paraIdStart: searchText(locator.paraIdStart || locatorParams.paraIdStart),
|
|
|
|
|
|
paraIdEnd: searchText(locator.paraIdEnd || locatorParams.paraIdEnd),
|
|
|
|
|
|
textFingerprint: searchText(locator.textFingerprint || locatorParams.textFingerprint),
|
2026-06-08 20:35:49 +08:00
|
|
|
|
evidenceText: locatorEvidenceText,
|
|
|
|
|
|
query: searchQueryText,
|
|
|
|
|
|
searchQuery: searchQueryText,
|
2026-06-05 23:00:53 +08:00
|
|
|
|
lineRange: evidenceLocatorLineRange(locator),
|
|
|
|
|
|
charRange: evidenceLocatorCharRange(locator)
|
2026-06-04 18:51:16 +08:00
|
|
|
|
});
|
2026-06-05 23:00:53 +08:00
|
|
|
|
if (!openedInResourceTab) console.warn('mnote evidence 搜索结果无法在当前页面资源标签打开', locator);
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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);
|
2026-06-09 09:20:56 +08:00
|
|
|
|
if (locatorParams.paragraphOrdinal != null) target.searchParams.set('paragraphOrdinal', searchText(locatorParams.paragraphOrdinal));
|
|
|
|
|
|
if (locatorParams.paraIdStart) target.searchParams.set('paraIdStart', searchText(locatorParams.paraIdStart));
|
|
|
|
|
|
if (locatorParams.paraIdEnd) target.searchParams.set('paraIdEnd', searchText(locatorParams.paraIdEnd));
|
|
|
|
|
|
if (locatorParams.textFingerprint) target.searchParams.set('textFingerprint', searchText(locatorParams.textFingerprint));
|
2026-06-04 18:51:16 +08:00
|
|
|
|
window.location.assign(target.pathname + target.search + target.hash);
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
window.location.assign(url);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function renderSearchRecentState(overlay) {
|
|
|
|
|
|
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
|
|
|
|
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
|
|
|
|
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
|
|
|
|
|
if (options instanceof HTMLElement) options.hidden = true;
|
|
|
|
|
|
if (meta) meta.innerHTML = '<span>最近浏览</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
|
|
|
|
if (results) {
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-recent" data-testid="wolai-search-recent">暂无最近浏览</div>';
|
|
|
|
|
|
}
|
2026-06-08 20:35:49 +08:00
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
searchUiState.hasRendered = true;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scheduleSearchResultsRender() {
|
|
|
|
|
|
window.clearTimeout(searchRenderTimer);
|
|
|
|
|
|
searchRenderTimer = window.setTimeout(function() { void renderSearchResults(); }, 120);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function renderSearchResults() {
|
|
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
if (!(overlay instanceof HTMLElement)) return;
|
|
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
|
|
|
|
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
|
|
|
|
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
|
|
|
|
|
if (!input || !meta || !results) return;
|
|
|
|
|
|
var query = searchText(input.value);
|
|
|
|
|
|
if (!query) {
|
|
|
|
|
|
activeSearchRequestId += 1;
|
|
|
|
|
|
renderSearchRecentState(overlay);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (options instanceof HTMLElement) options.hidden = false;
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var knowledgeMode = searchSwitchValue(overlay, 'knowledge');
|
|
|
|
|
|
if (knowledgeMode && searchNonWhitespaceCharCount(query) < 2) {
|
|
|
|
|
|
activeSearchRequestId += 1;
|
|
|
|
|
|
meta.innerHTML = '<span>资料库检索</span><span>请输入至少 2 个字再搜索</span>';
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">请输入至少 2 个字再搜索</div>';
|
|
|
|
|
|
window.__mnoteSearchResults = [];
|
|
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
searchUiState.hasRendered = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var requestId = ++activeSearchRequestId;
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var knowledgeRequestMode = knowledgeMode ? knowledgeSearchRequestMode(overlay) : '';
|
2026-05-25 17:36:17 +08:00
|
|
|
|
meta.innerHTML = '<span>搜索中…</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-loading="true">搜索中…</div>';
|
|
|
|
|
|
try {
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var response = await fetch(knowledgeMode ? '/api/knowledge-rag/search' : '/api/search/documents', {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
2026-06-08 20:35:49 +08:00
|
|
|
|
body: JSON.stringify(knowledgeMode ? {
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
|
|
|
|
rootUri: currentRootUri() || null,
|
|
|
|
|
|
query: query,
|
2026-06-09 09:20:56 +08:00
|
|
|
|
mode: knowledgeRequestMode,
|
2026-06-08 20:35:49 +08:00
|
|
|
|
topK: 12,
|
|
|
|
|
|
chunkTopK: 24,
|
|
|
|
|
|
includeChunkContent: true
|
|
|
|
|
|
} : {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
|
|
|
|
sourceKind: currentSourceKind() || null,
|
|
|
|
|
|
rootUri: currentRootUri() || null,
|
2026-06-05 23:00:53 +08:00
|
|
|
|
documentId: searchSwitchValue(overlay, 'page') ? (currentSearchDocumentId() || null) : (currentDocumentId() || null),
|
2026-05-25 17:36:17 +08:00
|
|
|
|
query: query,
|
|
|
|
|
|
limit: 30,
|
|
|
|
|
|
filters: {
|
|
|
|
|
|
titleOnly: searchSwitchValue(overlay, 'title'),
|
|
|
|
|
|
exact: searchSwitchValue(overlay, 'exact'),
|
2026-06-08 20:35:49 +08:00
|
|
|
|
includeOcr: false,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
onlyCurrentPage: searchSwitchValue(overlay, 'page'),
|
|
|
|
|
|
timeRange: 'any',
|
|
|
|
|
|
timeField: 'updated'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
var payload = await response.json();
|
|
|
|
|
|
if (requestId !== activeSearchRequestId) return;
|
|
|
|
|
|
var items = Array.isArray(payload.results) ? payload.results : [];
|
2026-06-09 09:20:56 +08:00
|
|
|
|
var ownerLabel = knowledgeMode ? ('资料库检索 · ' + knowledgeSearchModeLabel(payload.retrievalMode || knowledgeRequestMode)) : '工作区搜索';
|
|
|
|
|
|
meta.innerHTML = '<span>' + ownerLabel + ' · 共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (!items.length) {
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
2026-06-08 20:35:49 +08:00
|
|
|
|
window.__mnoteSearchResults = [];
|
|
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
searchUiState.hasRendered = true;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-04 18:51:16 +08:00
|
|
|
|
window.__mnoteSearchResults = items;
|
2026-06-08 20:35:49 +08:00
|
|
|
|
results.innerHTML = renderSearchResultsHtml(items, overlay, query);
|
|
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
searchUiState.hasRendered = true;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (requestId !== activeSearchRequestId) return;
|
|
|
|
|
|
meta.innerHTML = '<span>共 0 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
2026-06-08 20:35:49 +08:00
|
|
|
|
window.__mnoteSearchResults = [];
|
|
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
searchUiState.hasRendered = true;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isSearchModalOpen() {
|
|
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
return overlay instanceof HTMLElement && !overlay.hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openSearchModal() {
|
|
|
|
|
|
var overlay = ensureSearchModal();
|
|
|
|
|
|
overlay.hidden = false;
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-search-modal-open', 'true');
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var restored = restoreSearchUiState(overlay);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
if (input) input.setAttribute('placeholder', '在 ' + currentWorkspaceName() + ' 中搜索');
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var query = input && 'value' in input ? searchText(input.value) : '';
|
|
|
|
|
|
var signature = query ? searchRequestSignature(overlay, query) : '';
|
|
|
|
|
|
if (!restored || signature !== searchUiState.signature) void renderSearchResults();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (input) {
|
|
|
|
|
|
setTimeout(function() { input.focus(); input.select(); }, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSearchModal() {
|
|
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
2026-06-08 20:35:49 +08:00
|
|
|
|
if (overlay instanceof HTMLElement) {
|
|
|
|
|
|
saveSearchUiState(overlay);
|
|
|
|
|
|
overlay.hidden = true;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
document.documentElement.removeAttribute('data-mnote-search-modal-open');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleSearchModal() {
|
|
|
|
|
|
if (isSearchModalOpen()) closeSearchModal();
|
|
|
|
|
|
else openSearchModal();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
function isSearchShortcutEvent(event) {
|
|
|
|
|
|
return Boolean(event && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && event.key && event.key.toLowerCase() === 'p');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function(event) {
|
|
|
|
|
|
if (!isSearchShortcutEvent(event)) return;
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
openSearchModal();
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
2026-05-26 04:01:52 +08:00
|
|
|
|
const sidebarPageAi = createSidebarPageAiRuntime({
|
|
|
|
|
|
buildLocalFileOpenUrl,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentPageAggregate,
|
|
|
|
|
|
currentPageOptions,
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
escapeHtml,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
openLocalResourceInActiveTab,
|
|
|
|
|
|
pageUiState,
|
|
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
searchText,
|
|
|
|
|
|
});
|
|
|
|
|
|
const openPageAiDrawer = (...args) => sidebarPageAi.openPageAiDrawer(...args);
|
|
|
|
|
|
const closePageAiDrawer = (...args) => sidebarPageAi.closePageAiDrawer(...args);
|
|
|
|
|
|
const isPageAiDrawerOpen = (...args) => sidebarPageAi.isPageAiDrawerOpen(...args);
|
|
|
|
|
|
const ensurePageAiDrawer = (...args) => sidebarPageAi.ensurePageAiDrawer(...args);
|
|
|
|
|
|
const sendPageAiMessage = (...args) => sidebarPageAi.sendPageAiMessage(...args);
|
|
|
|
|
|
const pageAiOpenHermesSettings = (...args) => sidebarPageAi.pageAiOpenHermesSettings(...args);
|
|
|
|
|
|
const pageAiStopRun = (...args) => sidebarPageAi.pageAiStopRun(...args);
|
|
|
|
|
|
const pageAiLoadGatewayHealth = (...args) => sidebarPageAi.pageAiLoadGatewayHealth(...args);
|
|
|
|
|
|
const renderPageAiControls = (...args) => sidebarPageAi.renderPageAiControls(...args);
|
|
|
|
|
|
const renderPageAiConversation = (...args) => sidebarPageAi.renderPageAiConversation(...args);
|
|
|
|
|
|
const renderPageAiProviderButtons = (...args) => sidebarPageAi.renderPageAiProviderButtons(...args);
|
|
|
|
|
|
const renderPageAiSuggestions = (...args) => sidebarPageAi.renderPageAiSuggestions(...args);
|
|
|
|
|
|
const pageAiSaveProfileMemory = (...args) => sidebarPageAi.pageAiSaveProfileMemory(...args);
|
|
|
|
|
|
const pageAiToggleSkill = (...args) => sidebarPageAi.pageAiToggleSkill(...args);
|
|
|
|
|
|
const pageAiToggleTool = (...args) => sidebarPageAi.pageAiToggleTool(...args);
|
|
|
|
|
|
const pageAiResumeBackendSession = (...args) => sidebarPageAi.pageAiResumeBackendSession(...args);
|
|
|
|
|
|
const pageAiRenameBackendSession = (...args) => sidebarPageAi.pageAiRenameBackendSession(...args);
|
|
|
|
|
|
const pageAiDeleteBackendSession = (...args) => sidebarPageAi.pageAiDeleteBackendSession(...args);
|
|
|
|
|
|
const pageAiResolvePermission = (...args) => sidebarPageAi.pageAiResolvePermission(...args);
|
|
|
|
|
|
const pageAiOpenLocation = (...args) => sidebarPageAi.pageAiOpenLocation(...args);
|
|
|
|
|
|
const pageAiSetActiveSession = (...args) => sidebarPageAi.pageAiSetActiveSession(...args);
|
|
|
|
|
|
const pageAiStartNewSession = (...args) => sidebarPageAi.pageAiStartNewSession(...args);
|
|
|
|
|
|
const pageAiLoadSessions = (...args) => sidebarPageAi.pageAiLoadSessions(...args);
|
|
|
|
|
|
const pageAiLoadBackendSessions = (...args) => sidebarPageAi.pageAiLoadBackendSessions(...args);
|
|
|
|
|
|
const pageAiCancelQueuedRun = (...args) => sidebarPageAi.pageAiCancelQueuedRun(...args);
|
|
|
|
|
|
const pageAiSearchBackendSessions = (...args) => sidebarPageAi.pageAiSearchBackendSessions(...args);
|
|
|
|
|
|
const pageAiPersistSessions = (...args) => sidebarPageAi.pageAiPersistSessions(...args);
|
|
|
|
|
|
const pageAiLoadProfiles = (...args) => sidebarPageAi.pageAiLoadProfiles(...args);
|
|
|
|
|
|
const pageAiLoadProfileMemory = (...args) => sidebarPageAi.pageAiLoadProfileMemory(...args);
|
|
|
|
|
|
const pageAiLoadSkills = (...args) => sidebarPageAi.pageAiLoadSkills(...args);
|
|
|
|
|
|
const pageAiSwitchProfile = (...args) => sidebarPageAi.pageAiSwitchProfile(...args);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
const pageAiSetSkillSource = (...args) => sidebarPageAi.pageAiSetSkillSource(...args);
|
2026-05-29 21:57:29 +08:00
|
|
|
|
const pageAiToggleSkillGroup = (...args) => sidebarPageAi.pageAiToggleSkillGroup(...args);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
const pageAiSetReasonixMemoryEnabled = (...args) => sidebarPageAi.pageAiSetReasonixMemoryEnabled(...args);
|
2026-05-29 21:57:29 +08:00
|
|
|
|
const pageAiSetHideHermesBuiltinSkills = (...args) => sidebarPageAi.pageAiSetHideHermesBuiltinSkills(...args);
|
2026-05-26 04:01:52 +08:00
|
|
|
|
const pageAiSetContextScope = (...args) => sidebarPageAi.pageAiSetContextScope(...args);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
const pageAiSetAgentId = (...args) => sidebarPageAi.pageAiSetAgentId(...args);
|
|
|
|
|
|
const pageAiToggleContextRef = (...args) => sidebarPageAi.pageAiToggleContextRef(...args);
|
2026-05-29 21:57:29 +08:00
|
|
|
|
const pageAiSetAgentPopoverOpen = (...args) => sidebarPageAi.pageAiSetAgentPopoverOpen(...args);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
const pageAiSetTargetPopoverOpen = (...args) => sidebarPageAi.pageAiSetTargetPopoverOpen(...args);
|
|
|
|
|
|
const pageAiSelectTarget = (...args) => sidebarPageAi.pageAiSelectTarget(...args);
|
2026-05-26 04:01:52 +08:00
|
|
|
|
const updatePageAiTriggerState = (...args) => sidebarPageAi.updatePageAiTriggerState(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
2026-05-26 02:05:36 +08:00
|
|
|
|
const sidebarAttachmentOpen = createSidebarAttachmentOpenRuntime({
|
|
|
|
|
|
attachmentClassForFileName: (...args) => attachmentClassForFileName(...args),
|
|
|
|
|
|
buildLocalFileOpenUrl,
|
|
|
|
|
|
buildLocalOnlyOfficeOpenUrl,
|
|
|
|
|
|
buildOnlyOfficeOpenPath,
|
|
|
|
|
|
buildOnlyOfficeOpenUrl,
|
2026-05-28 22:01:44 +08:00
|
|
|
|
buildPdfPreviewOpenUrl,
|
2026-05-27 11:31:12 +08:00
|
|
|
|
closestAction,
|
2026-05-26 02:05:36 +08:00
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
currentWorkspaceSourcePayload,
|
|
|
|
|
|
fileTreeIconKindForFileName,
|
2026-05-26 07:08:26 +08:00
|
|
|
|
healLegacyOfficeAttachmentParagraphs,
|
2026-05-28 22:01:44 +08:00
|
|
|
|
hydrateEditorAttachmentMeta,
|
2026-05-26 02:05:36 +08:00
|
|
|
|
inferCodeAttachmentLanguage: (...args) => inferCodeAttachmentLanguage(...args),
|
|
|
|
|
|
inferOnlyOfficeFileType,
|
|
|
|
|
|
isCodeAttachmentFileName: (...args) => isCodeAttachmentFileName(...args),
|
|
|
|
|
|
isPdfAttachmentFileName: (...args) => isPdfAttachmentFileName(...args),
|
|
|
|
|
|
localFilePathFromAssetId,
|
|
|
|
|
|
openLocalOfficeFileInActiveTab,
|
|
|
|
|
|
openLocalResourceInActiveTab,
|
|
|
|
|
|
openTreeContextMenu: (...args) => openTreeContextMenu(...args),
|
|
|
|
|
|
resolveWorkspaceId,
|
|
|
|
|
|
triggerBrowserDownload: (...args) => triggerBrowserDownload(...args),
|
|
|
|
|
|
uploadedFileSize,
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
2026-05-26 02:05:36 +08:00
|
|
|
|
const refreshEditorLocalAttachmentExistence = (...args) => sidebarAttachmentOpen.refreshEditorLocalAttachmentExistence(...args);
|
|
|
|
|
|
const detailFromEditorAttachmentLink = (...args) => sidebarAttachmentOpen.detailFromEditorAttachmentLink(...args);
|
|
|
|
|
|
const enhanceEditorAttachmentLink = (...args) => sidebarAttachmentOpen.enhanceEditorAttachmentLink(...args);
|
|
|
|
|
|
const enhanceEditorAttachmentLinks = (...args) => sidebarAttachmentOpen.enhanceEditorAttachmentLinks(...args);
|
|
|
|
|
|
const openEditorAttachmentDetail = (...args) => sidebarAttachmentOpen.openEditorAttachmentDetail(...args);
|
|
|
|
|
|
const openEditorAttachmentNewWindow = (...args) => sidebarAttachmentOpen.openEditorAttachmentNewWindow(...args);
|
|
|
|
|
|
const openEditorAttachmentEditTab = (...args) => sidebarAttachmentOpen.openEditorAttachmentEditTab(...args);
|
|
|
|
|
|
const openCodeEditorAttachment = (...args) => sidebarAttachmentOpen.openCodeEditorAttachment(...args);
|
|
|
|
|
|
const openEditorAttachmentDownload = (...args) => sidebarAttachmentOpen.openEditorAttachmentDownload(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
document.addEventListener('click', function(e) {
|
|
|
|
|
|
if (activeTreeContextMenu && activeTreeContextMenu.contains(e.target)) return;
|
|
|
|
|
|
if (activeTreeContextMenu) closeTreeContextMenu();
|
|
|
|
|
|
|
|
|
|
|
|
var sidebarToggle = closestAction(e.target, '[data-mnote-action="toggle-sidebar"]');
|
|
|
|
|
|
if (sidebarToggle) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
toggleWorkspaceSidebar(sidebarToggle);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var attachmentAction = closestAction(e.target, '[data-attachment-action]');
|
|
|
|
|
|
if (attachmentAction) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
var link = activeEditorAttachmentLink;
|
|
|
|
|
|
if (!(link instanceof HTMLAnchorElement)) return;
|
|
|
|
|
|
var attachmentDetail = detailFromEditorAttachmentLink(link);
|
|
|
|
|
|
var attachmentActionName = attachmentAction.getAttribute('data-attachment-action') || '';
|
|
|
|
|
|
if (attachmentActionName === 'download') {
|
|
|
|
|
|
openEditorAttachmentDownload(attachmentDetail);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (attachmentActionName === 'menu') {
|
|
|
|
|
|
openEditorAttachmentMenu(link, attachmentAction);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 11:13:05 +08:00
|
|
|
|
var editorAttachmentLink = closestAction(e.target, '.editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (editorAttachmentLink instanceof HTMLAnchorElement) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openEditorAttachmentLink(editorAttachmentLink);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var historyClose = closestAction(e.target, '[data-page-history-action="close"]');
|
|
|
|
|
|
if (historyClose) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
closePageHistoryDrawer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var shareClose = closestAction(e.target, '[data-page-share-action="close"]');
|
|
|
|
|
|
if (shareClose) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
closePageShareDialog();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var shareCopy = closestAction(e.target, '[data-page-share-action="copy-link"]');
|
|
|
|
|
|
if (shareCopy) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void copyTreeContextValue(window.location.href, 'page-share-link');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageHistoryTrigger = closestAction(e.target, '[data-mnote-action="open-page-history"]');
|
|
|
|
|
|
if (pageHistoryTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openPageHistoryDrawer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-page-settings"]');
|
|
|
|
|
|
if (pageSettingsTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
togglePageSettingsPopover();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
var indexSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-index-settings"]');
|
|
|
|
|
|
if (indexSettingsTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
2026-06-07 01:10:31 +08:00
|
|
|
|
openKnowledgeRagSettingsPopover();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var knowledgeRagSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-knowledge-rag-settings"]');
|
|
|
|
|
|
if (knowledgeRagSettingsTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openKnowledgeRagSettingsPopover();
|
2026-06-04 18:51:16 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var settingsClose = closestAction(e.target, '[data-settings-action="close"]');
|
|
|
|
|
|
if (settingsClose) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
closeAllSettingsPopovers();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var pageSettingsPanel = closestAction(e.target, '.wolai-page-settings-panel');
|
2026-06-04 18:51:16 +08:00
|
|
|
|
if (isAnySettingsOpen() && !pageSettingsPanel) {
|
|
|
|
|
|
closeAllSettingsPopovers();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageSettingsTab = closestAction(e.target, '[data-page-settings-tab]');
|
|
|
|
|
|
if (pageSettingsTab) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setActivePageSettingsTab(pageSettingsTab.getAttribute('data-page-settings-tab') || 'page');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 01:10:31 +08:00
|
|
|
|
var knowledgeRagAction = closestAction(e.target, '[data-knowledge-rag-action]');
|
|
|
|
|
|
if (knowledgeRagAction) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var knowledgeRagActionName = knowledgeRagAction.getAttribute('data-knowledge-rag-action') || '';
|
|
|
|
|
|
if (knowledgeRagActionName === 'refresh') {
|
|
|
|
|
|
void loadKnowledgeRagStatus(true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'add-source') {
|
|
|
|
|
|
addKnowledgeRagSourceInput();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'remove-source') {
|
|
|
|
|
|
removeKnowledgeRagSourceInput(knowledgeRagAction);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'use-filetree-selection') {
|
|
|
|
|
|
useKnowledgeRagFileTreeSelection();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'ingest') {
|
|
|
|
|
|
void ingestKnowledgeRagSources();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'reindex-source') {
|
|
|
|
|
|
void ingestSingleKnowledgeRagSource(knowledgeRagAction.getAttribute('data-knowledge-rag-source-path') || '');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'delete-source') {
|
|
|
|
|
|
void deleteKnowledgeRagSource(knowledgeRagAction.getAttribute('data-knowledge-rag-source-path') || '');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'prune') {
|
|
|
|
|
|
void pruneKnowledgeRagRegistry();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'filter-sources') {
|
|
|
|
|
|
setKnowledgeRagSourceFilter(knowledgeRagAction.getAttribute('data-knowledge-rag-filter') || 'attention');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (knowledgeRagActionName === 'open-dashboard') {
|
|
|
|
|
|
openKnowledgeRagDashboard();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var pageSettingsAction = closestAction(e.target, '[data-page-settings-action]');
|
|
|
|
|
|
if (pageSettingsAction) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var actionName = pageSettingsAction.getAttribute('data-page-settings-action') || '';
|
|
|
|
|
|
if (actionName === 'history') {
|
|
|
|
|
|
openPageHistoryDrawer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (actionName === 'share') {
|
|
|
|
|
|
openPageShareDialog();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiTrigger = closestAction(e.target, '[data-mnote-action="open-page-ai"]');
|
|
|
|
|
|
if (pageAiTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openPageAiDrawer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
|
var navigationPageTrigger = closestAction(e.target, '[data-mnote-action="open-navigation-page"]');
|
|
|
|
|
|
if (navigationPageTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openCurrentNavigationPage(navigationPageTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var searchTrigger = closestAction(e.target, '[data-mnote-action="open-search-modal"]');
|
|
|
|
|
|
if (searchTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
toggleSearchModal();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var searchDialog = closestAction(e.target, '.wolai-search-dialog');
|
|
|
|
|
|
if (isSearchModalOpen() && !searchDialog) {
|
|
|
|
|
|
closeSearchModal();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-08 20:35:49 +08:00
|
|
|
|
var searchSourceToggle = closestAction(e.target, '[data-search-source-toggle]');
|
|
|
|
|
|
if (searchSourceToggle) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var sourceGroup = searchSourceToggle.closest('.wolai-search-source-group');
|
|
|
|
|
|
var sourceResults = sourceGroup && sourceGroup.querySelector('[data-search-source-results="true"]');
|
|
|
|
|
|
var sourceKey = sourceGroup ? searchText(sourceGroup.getAttribute('data-search-source-key')) : '';
|
|
|
|
|
|
var nextCollapsed = !(sourceResults instanceof HTMLElement && sourceResults.hidden);
|
|
|
|
|
|
if (sourceResults instanceof HTMLElement) sourceResults.hidden = nextCollapsed;
|
|
|
|
|
|
searchSourceToggle.setAttribute('aria-expanded', nextCollapsed ? 'false' : 'true');
|
|
|
|
|
|
var chevron = searchSourceToggle.querySelector('.wolai-search-source-chevron');
|
|
|
|
|
|
if (chevron) chevron.textContent = nextCollapsed ? '展开' : '收起';
|
|
|
|
|
|
if (sourceKey) searchUiState.sourceCollapsed[sourceKey] = nextCollapsed;
|
|
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
if (overlay instanceof HTMLElement) saveSearchUiState(overlay);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-04 18:51:16 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
var sourceMenuTrigger = closestAction(e.target, '[data-mnote-action="open-workspace-source-menu"]');
|
|
|
|
|
|
if (sourceMenuTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var existingSourceMenu = document.querySelector('[data-testid="mnote-workspace-source-menu"]');
|
|
|
|
|
|
if (existingSourceMenu) closeWorkspaceSourceMenu();
|
|
|
|
|
|
else openWorkspaceSourceMenu(sourceMenuTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var sourceMenu = closestAction(e.target, '[data-testid="mnote-workspace-source-menu"]');
|
|
|
|
|
|
if (!sourceMenu) closeWorkspaceSourceMenu();
|
|
|
|
|
|
|
|
|
|
|
|
var accountMenuTrigger = closestAction(e.target, '[data-mnote-action="open-account-menu"]');
|
|
|
|
|
|
if (accountMenuTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var existingAccountMenu = document.querySelector('[data-testid="mnote-account-menu"]');
|
|
|
|
|
|
if (existingAccountMenu) closeAccountMenu();
|
|
|
|
|
|
else openAccountMenu(accountMenuTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var accountMenu = closestAction(e.target, '[data-testid="mnote-account-menu"]');
|
|
|
|
|
|
if (!accountMenu) closeAccountMenu();
|
|
|
|
|
|
|
|
|
|
|
|
var trashTrigger = closestAction(e.target, '[data-mnote-action="open-trash-modal"]');
|
|
|
|
|
|
if (trashTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
openTrashModal(trashTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var trashClose = closestAction(e.target, '[data-mnote-trash-modal-close]');
|
|
|
|
|
|
if (trashClose) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
closeTrashModal();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var tabTrigger = closestAction(e.target, '[data-mnote-sidebar-tree-tab]');
|
|
|
|
|
|
if (tabTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
switchSidebarTreeTab(tabTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
var pageShortcutTrigger = closestAction(e.target, '[data-mnote-action="toggle-sidebar-shortcut"]');
|
|
|
|
|
|
if (pageShortcutTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void toggleCurrentPageSidebarShortcut(pageShortcutTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var shortcutMenu = closestAction(e.target, '[data-testid="mnote-sidebar-shortcut-menu"]');
|
|
|
|
|
|
var shortcutMenuAction = closestAction(e.target, '[data-mnote-sidebar-shortcut-menu-action]');
|
|
|
|
|
|
if (shortcutMenuAction) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var action = String(shortcutMenuAction.getAttribute('data-mnote-sidebar-shortcut-menu-action') || '').trim();
|
|
|
|
|
|
var shortcutRowFromMenu = shortcutMenu && shortcutMenu.__mnoteShortcutRow instanceof HTMLElement
|
|
|
|
|
|
? shortcutMenu.__mnoteShortcutRow
|
|
|
|
|
|
: null;
|
|
|
|
|
|
if (action === 'remove') {
|
|
|
|
|
|
void removeSidebarShortcutByRow(shortcutRowFromMenu);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (action === 'open') {
|
|
|
|
|
|
closeSidebarShortcutMenu();
|
|
|
|
|
|
if (shortcutRowFromMenu && shortcutRowFromMenu.getAttribute('data-mnote-shortcut-kind') === 'folder') {
|
|
|
|
|
|
void openStarredFolderShortcut(shortcutRowFromMenu);
|
|
|
|
|
|
} else if (shortcutRowFromMenu && shortcutRowFromMenu.getAttribute('href')) {
|
|
|
|
|
|
window.location.assign(shortcutRowFromMenu.getAttribute('href'));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (action === 'copy-link') {
|
|
|
|
|
|
var href = shortcutRowFromMenu && shortcutRowFromMenu.getAttribute('href') || window.location.href;
|
|
|
|
|
|
if (navigator.clipboard && href) void navigator.clipboard.writeText(new URL(href, window.location.origin).toString());
|
|
|
|
|
|
closeSidebarShortcutMenu();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!shortcutMenu) closeSidebarShortcutMenu();
|
|
|
|
|
|
|
|
|
|
|
|
var shortcutMenuTrigger = closestAction(e.target, '.wolai-starred-section [data-mnote-shortcut-action="menu"]');
|
|
|
|
|
|
if (shortcutMenuTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var shortcutRow = shortcutMenuTrigger.closest('[data-mnote-shortcut-id]');
|
|
|
|
|
|
openSidebarShortcutMenu(shortcutRow, shortcutMenuTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var folderShortcutRow = closestAction(e.target, '.wolai-starred-section [data-mnote-shortcut-kind="folder"]');
|
|
|
|
|
|
if (folderShortcutRow) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void openStarredFolderShortcut(folderShortcutRow);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var localFolderTrigger = closestAction(e.target, '[data-mnote-action="open-local-folder"]');
|
|
|
|
|
|
if (localFolderTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
requestOpenLocalFolder();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var createLocalWorkspaceTrigger = closestAction(e.target, '[data-mnote-action="create-local-workspace"]');
|
|
|
|
|
|
if (createLocalWorkspaceTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void createDefaultLocalWorkspace(createLocalWorkspaceTrigger);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var createTrigger = closestAction(e.target, '[data-mnote-action="create-page"]');
|
|
|
|
|
|
if (createTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void createPage(createTrigger, null);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
var createFolderTrigger = closestAction(e.target, '[data-mnote-action="create-folder"]');
|
|
|
|
|
|
if (createFolderTrigger) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (currentSourceKind() !== 'local_folder') return;
|
|
|
|
|
|
var scope = currentFileTreeScope();
|
|
|
|
|
|
var parentId = scope ? 'local:folder:' + scope : null;
|
|
|
|
|
|
void createFileTreeFolder(createFolderTrigger, parentId);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var fileTree = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (fileTree && fileTree.contains(e.target)) {
|
|
|
|
|
|
var fileBtn = closestAction(e.target, '[data-rust-action]');
|
|
|
|
|
|
var fileRow = closestAction(e.target, '.tree-row[data-shell-mode="filetree"]');
|
|
|
|
|
|
if (!fileRow) return;
|
|
|
|
|
|
var fileAction = fileBtn ? fileBtn.getAttribute('data-rust-action') : 'open';
|
|
|
|
|
|
var rowId = fileRow.getAttribute('data-row-id') || '';
|
|
|
|
|
|
var rowKind = fileRow.getAttribute('data-row-kind') || '';
|
|
|
|
|
|
var documentId = fileRow.getAttribute('data-document-id') || fileRow.getAttribute('data-doc-id') || '';
|
|
|
|
|
|
var ownerDocumentId = fileRow.getAttribute('data-owner-document-id') || documentId;
|
|
|
|
|
|
var assetId = fileRow.getAttribute('data-asset-id') || '';
|
|
|
|
|
|
var assetType = '';
|
|
|
|
|
|
var kindBadge = fileRow.querySelector('.tree-kind-badge');
|
|
|
|
|
|
if (kindBadge instanceof HTMLElement) {
|
|
|
|
|
|
assetType = kindBadge.getAttribute('data-kind') || '';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileAction === 'toggle') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
toggleChildren(fileRow, fileBtn);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileAction === 'create') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void createPage(fileBtn || fileRow, documentId || fileRow.getAttribute('data-node-id'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileAction === 'menu') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (rowId && !sidebarFileTreeSelection.selectedRowIds.has(rowId)) {
|
|
|
|
|
|
selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
var point = rowCenter(fileBtn || fileRow);
|
|
|
|
|
|
dispatchSidebarEvent('tree.filetree.context-menu', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null });
|
|
|
|
|
|
openFileTreeContextMenu(fileRow, point.x, point.y, fileBtn || fileRow);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileAction === 'open' && rowKind === 'folder') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
selectSidebarFileTreeRow(fileRow, { ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });
|
|
|
|
|
|
toggleChildren(fileRow, fileRow.querySelector('[data-rust-action="toggle"]'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
selectSidebarFileTreeRow(fileRow, { ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });
|
|
|
|
|
|
var objectIdentity = readFileTreeObjectIdentity(fileRow);
|
2026-05-28 22:01:44 +08:00
|
|
|
|
var workspacePath = readWorkspacePathFromRow(fileRow);
|
2026-06-02 17:17:49 +08:00
|
|
|
|
var localRelativePath = fileTreeRowLocalRelativePath(fileRow) || String(workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath) || '').trim();
|
2026-05-28 22:01:44 +08:00
|
|
|
|
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId || null, assetType: assetType || null, objectIdentity: objectIdentity, workspacePath: workspacePath, workspaceId: resolveWorkspaceId(fileRow) });
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-07 10:35:21 +08:00
|
|
|
|
if ((rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') && isRetiredOcrSidecarMarkdownPath(localRelativePath)) {
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-retired-ocr-sidecar-filetree-open', 'resource-tab');
|
|
|
|
|
|
var retiredOcrSidecarResourceInput = {
|
2026-06-02 17:17:49 +08:00
|
|
|
|
path: localRelativePath,
|
|
|
|
|
|
title: fileTreeRowTitleForShortcut(fileRow, localRelativePath),
|
|
|
|
|
|
kind: 'markdown',
|
2026-06-07 10:35:21 +08:00
|
|
|
|
objectIdentity: 'retired-ocr-sidecar:' + localRelativePath,
|
|
|
|
|
|
assetId: 'retired-ocr-sidecar:' + localRelativePath,
|
2026-06-02 17:17:49 +08:00
|
|
|
|
documentId: documentId || ownerDocumentId || null,
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(fileRow),
|
|
|
|
|
|
sourceKind: 'local_folder',
|
|
|
|
|
|
rootUri: currentRootUri(),
|
|
|
|
|
|
resourceKind: 'markdown',
|
|
|
|
|
|
workspacePath: workspacePath,
|
|
|
|
|
|
paneRole: 'primary'
|
|
|
|
|
|
};
|
|
|
|
|
|
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
2026-06-07 10:35:21 +08:00
|
|
|
|
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab(retiredOcrSidecarResourceInput);
|
2026-06-02 17:17:49 +08:00
|
|
|
|
} else {
|
2026-06-07 10:35:21 +08:00
|
|
|
|
void openLocalResourceInActiveTab(retiredOcrSidecarResourceInput);
|
2026-06-02 17:17:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if ((rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') && documentId) {
|
2026-05-28 22:01:44 +08:00
|
|
|
|
void recordNavigationRecent({
|
|
|
|
|
|
kind: 'page',
|
|
|
|
|
|
sourceKind: currentSourceKind() || 'local_folder',
|
|
|
|
|
|
rootUri: currentRootUri(),
|
|
|
|
|
|
relativePath: String(fileRow.getAttribute('data-local-relative-path') || '').trim(),
|
|
|
|
|
|
documentId: documentId,
|
|
|
|
|
|
title: fileTreeRowTitleForShortcut(fileRow, documentId),
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(fileRow)
|
|
|
|
|
|
});
|
2026-05-27 11:31:12 +08:00
|
|
|
|
navigateToDocument(documentId, resolveWorkspaceId(fileRow), { treeView: 'filetree', fileTreeScope: currentFileTreeScope() });
|
2026-05-25 17:36:17 +08:00
|
|
|
|
} else if (assetId) {
|
2026-05-28 22:01:44 +08:00
|
|
|
|
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId, assetType: assetType || null, objectIdentity: objectIdentity, workspacePath: workspacePath, workspaceId: resolveWorkspaceId(fileRow), openTarget: e.shiftKey || e.ctrlKey || e.metaKey ? 'new-window' : 'active-tab' });
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:35:08 +08:00
|
|
|
|
if (sidebarPageTree.handlePageTreeClick(e, { closestAction: closestAction })) return;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-27 11:31:12 +08:00
|
|
|
|
window.addEventListener('tree.sidebarShortcut.toggleFolder', function(event) {
|
|
|
|
|
|
var detail = event.detail || {};
|
|
|
|
|
|
var row = detail.rowId
|
|
|
|
|
|
? document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.rowId) + '"]')
|
|
|
|
|
|
: null;
|
|
|
|
|
|
void toggleFolderSidebarShortcut(detail, row);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('tree.filetree.internal-drop', function(event) {
|
|
|
|
|
|
var detail = event.detail || {};
|
|
|
|
|
|
var rowIds = Array.isArray(detail.rowIds) ? detail.rowIds : [];
|
|
|
|
|
|
if (!rowIds.length) return;
|
|
|
|
|
|
var targetRow = detail.targetRowId
|
|
|
|
|
|
? document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.targetRowId) + '"]')
|
|
|
|
|
|
: null;
|
|
|
|
|
|
recordFileTreeAction('internal-drop', {
|
|
|
|
|
|
rowId: detail.targetRowId || '',
|
|
|
|
|
|
sourceRowIds: rowIds,
|
|
|
|
|
|
copy: Boolean(detail.copy)
|
|
|
|
|
|
});
|
|
|
|
|
|
void moveSidebarFileTreeRows(rowIds, targetRow, { copy: Boolean(detail.copy) });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
document.addEventListener('contextmenu', function(event) {
|
2026-05-29 11:13:05 +08:00
|
|
|
|
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (editorAttachmentLink instanceof HTMLAnchorElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
enhanceEditorAttachmentLink(editorAttachmentLink);
|
|
|
|
|
|
openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var fileRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]');
|
|
|
|
|
|
if (fileRow) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
var contextRowId = fileRow.getAttribute('data-row-id') || '';
|
|
|
|
|
|
if (contextRowId && !sidebarFileTreeSelection.selectedRowIds.has(contextRowId)) {
|
|
|
|
|
|
selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
openFileTreeContextMenu(fileRow, event.clientX, event.clientY, fileRow);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-26 02:35:08 +08:00
|
|
|
|
if (sidebarPageTree.handlePageTreeContextMenu(event, { closestAction: closestAction })) return;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function(event) {
|
|
|
|
|
|
var keyTarget = event.target;
|
|
|
|
|
|
var fileTreeRootForKey = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
var fileTreeRowForKey = closestAction(keyTarget, '.tree-row[data-shell-mode="filetree"]');
|
|
|
|
|
|
if (!fileTreeRowForKey && fileTreeRootForKey && fileTreeRootForKey.contains(document.activeElement)) {
|
|
|
|
|
|
fileTreeRowForKey = closestAction(document.activeElement, '.tree-row[data-shell-mode="filetree"]');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileTreeRowForKey) {
|
|
|
|
|
|
var _kbRT_ = window.__mnoteFileTreeKeyboardRuntime;
|
|
|
|
|
|
if (_kbRT_ && typeof _kbRT_.handleFileTreeKeyDown === 'function') {
|
|
|
|
|
|
var handled = _kbRT_.handleFileTreeKeyDown(event, {
|
|
|
|
|
|
closestAction: closestAction,
|
|
|
|
|
|
beginFileTreeInlineRename: beginFileTreeInlineRename,
|
|
|
|
|
|
selectedSidebarFileTreeRows: selectedSidebarFileTreeRows,
|
|
|
|
|
|
buildSidebarFileTreeContext: buildSidebarFileTreeContext,
|
|
|
|
|
|
evaluateSidebarFileTreeWhen: evaluateSidebarFileTreeWhen,
|
|
|
|
|
|
deleteSelectedSidebarFileTreeRows: deleteSelectedSidebarFileTreeRows,
|
|
|
|
|
|
pasteSidebarFileTreeClipboard: pasteSidebarFileTreeClipboard,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (handled) return;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// inline fallback
|
|
|
|
|
|
if (event.key === 'F2') {
|
|
|
|
|
|
event.preventDefault();
|
2026-05-28 22:01:44 +08:00
|
|
|
|
var renameCtx = buildSidebarFileTreeContext('filetree', { targetRow: fileTreeRowForKey });
|
|
|
|
|
|
if (!evaluateSidebarFileTreeWhen(renameCtx, '!workspace.readonly && !editor.dirty')) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
beginFileTreeInlineRename(fileTreeRowForKey);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((event.ctrlKey || event.metaKey) && !event.altKey && event.key) {
|
|
|
|
|
|
var shortcutKey = event.key.toLowerCase();
|
|
|
|
|
|
if (shortcutKey === 'c' || shortcutKey === 'x') {
|
|
|
|
|
|
var selectedRows = selectedSidebarFileTreeRows();
|
|
|
|
|
|
var selectedRowIds = selectedRows.map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean);
|
|
|
|
|
|
if (selectedRowIds.length > 0) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
sidebarFileTreeClipboard = { action: shortcutKey === 'x' ? 'cut' : 'copy', rowIds: selectedRowIds };
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-filetree-clipboard-action', sidebarFileTreeClipboard.action);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (shortcutKey === 'v') {
|
|
|
|
|
|
event.preventDefault();
|
2026-05-28 22:01:44 +08:00
|
|
|
|
var pasteCtx = buildSidebarFileTreeContext('filetree', { targetRow: fileTreeRowForKey });
|
|
|
|
|
|
if (!evaluateSidebarFileTreeWhen(pasteCtx, '!workspace.readonly')) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
void pasteSidebarFileTreeClipboard(fileTreeRowForKey);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
|
|
|
|
event.preventDefault();
|
2026-05-28 22:01:44 +08:00
|
|
|
|
var delCtx = buildSidebarFileTreeContext('filetree', { targetRow: fileTreeRowForKey });
|
|
|
|
|
|
if (!evaluateSidebarFileTreeWhen(delCtx, '!workspace.readonly && !editor.dirty')) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
void deleteSelectedSidebarFileTreeRows(fileTreeRowForKey);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-04 18:51:16 +08:00
|
|
|
|
if (event.key === 'Escape' && isAnySettingsOpen()) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
event.preventDefault();
|
2026-06-04 18:51:16 +08:00
|
|
|
|
closeAllSettingsPopovers();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-08 20:35:49 +08:00
|
|
|
|
if (isSearchShortcutEvent(event)) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
event.preventDefault();
|
2026-06-08 20:35:49 +08:00
|
|
|
|
openSearchModal();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
|
|
closeTrashModal();
|
|
|
|
|
|
closeSearchModal();
|
|
|
|
|
|
closeTreeContextMenu();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('change', function(event) {
|
|
|
|
|
|
var globalCheckbox = closestAction(event.target, '[data-global-option-checkbox="showHeadingNumbers"]');
|
|
|
|
|
|
if (globalCheckbox instanceof HTMLInputElement) {
|
|
|
|
|
|
writeGlobalShowHeadingNumbers(globalCheckbox.checked);
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
renderPageSettingsPopover();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var checkbox = closestAction(event.target, '[data-page-option-checkbox]');
|
|
|
|
|
|
if (checkbox instanceof HTMLInputElement) {
|
|
|
|
|
|
var key = checkbox.getAttribute('data-page-option-checkbox') || '';
|
|
|
|
|
|
if (!pageOptionIsSupported(key)) return;
|
|
|
|
|
|
var patch = {};
|
|
|
|
|
|
patch[key] = checkbox.checked;
|
|
|
|
|
|
void persistPageOptionsPatch(patch);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var select = closestAction(event.target, '[data-page-option-select]');
|
|
|
|
|
|
if (select instanceof HTMLSelectElement) {
|
|
|
|
|
|
var selectKey = select.getAttribute('data-page-option-select') || '';
|
|
|
|
|
|
var next = {};
|
|
|
|
|
|
if (selectKey === 'layoutDensity') next.layoutDensity = select.value;
|
|
|
|
|
|
if (selectKey === 'pageFont') next.pageFont = select.value;
|
|
|
|
|
|
if (Object.keys(next).length) {
|
|
|
|
|
|
void persistPageOptionsPatch(next);
|
|
|
|
|
|
}
|
2026-05-28 22:01:44 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var pageWidthSelect = closestAction(event.target, '[data-page-width-select]');
|
|
|
|
|
|
if (pageWidthSelect instanceof HTMLSelectElement) {
|
|
|
|
|
|
var pageWidthType = pageWidthSelect.getAttribute('data-page-width-select') || '';
|
|
|
|
|
|
void persistPageWidthPreference(pageWidthType, pageWidthSelect.value);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-06-01 09:29:12 +08:00
|
|
|
|
sidebarPageAi.installPageAiDelegates();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
function initializePageUiSurfaces() {
|
|
|
|
|
|
pageUiState.pageOptions = null;
|
2026-05-28 22:01:44 +08:00
|
|
|
|
pageUiState.pageWidthPreferences = null;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
applyPageOptionsToShell();
|
2026-05-28 22:01:44 +08:00
|
|
|
|
void loadPageWidthPreferences();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
updatePageSettingsTriggerState();
|
|
|
|
|
|
updatePageAiTriggerState();
|
|
|
|
|
|
ensureHistorySnapshotsSeeded();
|
|
|
|
|
|
installWorkspaceSidebarResizer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.__mnoteRecordPageHistorySnapshot = recordPageHistorySnapshot;
|
|
|
|
|
|
window.__mnoteApplyPageOptionsToShell = applyPageOptionsToShell;
|
|
|
|
|
|
function scheduleInitializePageUiSurfaces() {
|
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
setTimeout(initializePageUiSurfaces, 0);
|
|
|
|
|
|
}, { once: true });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setTimeout(initializePageUiSurfaces, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
scheduleInitializePageUiSurfaces();
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
function mnoteDevHotReloadEnabled() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return new URL(import.meta.url).searchParams.has('devHot');
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 17:36:17 +08:00
|
|
|
|
function installMnoteDevHotReload() {
|
|
|
|
|
|
var bootId = '';
|
|
|
|
|
|
var failedOnce = false;
|
|
|
|
|
|
var timer = 0;
|
|
|
|
|
|
function tick() {
|
|
|
|
|
|
fetch('/api/dev/hot-reload', { cache: 'no-store', headers: { accept: 'application/json' } })
|
|
|
|
|
|
.then(function(response) { return response.ok ? response.json() : null; })
|
|
|
|
|
|
.then(function(payload) {
|
|
|
|
|
|
if (!payload || payload.enabled !== true || !payload.bootId) {
|
|
|
|
|
|
if (!bootId && timer) window.clearInterval(timer);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-dev-hot-reload', 'enabled');
|
|
|
|
|
|
if (!bootId) {
|
|
|
|
|
|
bootId = String(payload.bootId);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (failedOnce || String(payload.bootId) !== bootId) {
|
|
|
|
|
|
window.location.assign(window.location.href);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(function() {
|
|
|
|
|
|
if (bootId) failedOnce = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
tick();
|
|
|
|
|
|
timer = window.setInterval(tick, 1000);
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
if (mnoteDevHotReloadEnabled()) {
|
|
|
|
|
|
installMnoteDevHotReload();
|
|
|
|
|
|
}
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
document.addEventListener('dragstart', function(event) {
|
|
|
|
|
|
var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"][draggable="true"]');
|
|
|
|
|
|
if (pageRow) {
|
2026-05-26 02:35:08 +08:00
|
|
|
|
startPageDrag(pageRow, event);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var fileRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"][draggable="true"]');
|
|
|
|
|
|
if (fileRow) {
|
|
|
|
|
|
var _dndRT_ = window.__mnoteFileTreeDndRuntime;
|
|
|
|
|
|
if (_dndRT_ && typeof _dndRT_.startFileTreeDrag === 'function') {
|
|
|
|
|
|
var dragInfo = _dndRT_.startFileTreeDrag(fileRow, {
|
|
|
|
|
|
selectedSidebarFileTreeRowIdsForDrag: selectedSidebarFileTreeRowIdsForDrag,
|
|
|
|
|
|
});
|
|
|
|
|
|
draggingFileTreeRowIds = _dndRT_.draggingFileTreeRowIds;
|
|
|
|
|
|
if (event.dataTransfer && dragInfo) {
|
|
|
|
|
|
event.dataTransfer.effectAllowed = dragInfo.effectAllowed || 'copyMove';
|
|
|
|
|
|
event.dataTransfer.setData(_dndRT_.FILETREE_DRAG_MIME, dragInfo.payload);
|
|
|
|
|
|
event.dataTransfer.setData('application/x-mnote-file-tree', dragInfo.payload);
|
|
|
|
|
|
event.dataTransfer.setData('text/plain', dragInfo.payload);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// inline fallback
|
|
|
|
|
|
draggingFileTreeRowIds = selectedSidebarFileTreeRowIdsForDrag(fileRow);
|
|
|
|
|
|
if (event.dataTransfer) {
|
|
|
|
|
|
var payload = JSON.stringify({ type: 'mnote-file-tree-dnd', version: 1, rowIds: draggingFileTreeRowIds });
|
|
|
|
|
|
event.dataTransfer.effectAllowed = 'copyMove';
|
|
|
|
|
|
event.dataTransfer.setData(FILETREE_DRAG_MIME, payload);
|
|
|
|
|
|
event.dataTransfer.setData('application/x-mnote-file-tree', payload);
|
|
|
|
|
|
event.dataTransfer.setData('text/plain', payload);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('dragover', function(event) {
|
|
|
|
|
|
var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"]');
|
|
|
|
|
|
var sourceNodeId = readPageDragNodeId(event);
|
|
|
|
|
|
if (pageRow && canDropPage(sourceNodeId, pageRow)) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
clearPageDropFeedback();
|
|
|
|
|
|
pageRow.setAttribute('data-drop-feedback', 'true');
|
|
|
|
|
|
pageRow.setAttribute('data-drop-position', pageDropPosition(event, pageRow));
|
2026-05-26 02:35:08 +08:00
|
|
|
|
setActivePageDropRow(pageRow);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var _dndRT_ = window.__mnoteFileTreeDndRuntime;
|
|
|
|
|
|
if (_dndRT_ && typeof _dndRT_.handleFileTreeDragOver === 'function') {
|
|
|
|
|
|
if (_dndRT_.handleFileTreeDragOver(event, { closestAction: closestAction })) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// inline fallback
|
|
|
|
|
|
var fileTree = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (fileTree && fileTree.contains(event.target)) {
|
|
|
|
|
|
var hasFiles = event.dataTransfer && Array.prototype.indexOf.call(event.dataTransfer.types || [], 'Files') >= 0;
|
|
|
|
|
|
var hasInternal = event.dataTransfer && Array.prototype.indexOf.call(event.dataTransfer.types || [], FILETREE_DRAG_MIME) >= 0;
|
|
|
|
|
|
if (!hasFiles && !hasInternal && draggingFileTreeRowIds.length === 0) return;
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false');
|
|
|
|
|
|
activeFileTreeDropRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]') || fileTree;
|
|
|
|
|
|
if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'true');
|
|
|
|
|
|
var copyModifier = event.altKey || event.ctrlKey || event.metaKey;
|
|
|
|
|
|
if (event.dataTransfer) event.dataTransfer.dropEffect = hasFiles || copyModifier ? 'copy' : 'move';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('drop', function(event) {
|
|
|
|
|
|
var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"]');
|
|
|
|
|
|
var sourceNodeId = readPageDragNodeId(event);
|
|
|
|
|
|
if (pageRow && canDropPage(sourceNodeId, pageRow)) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
var position = pageDropPosition(event, pageRow);
|
|
|
|
|
|
var target = resolvePageMoveTarget(pageRow, position);
|
2026-05-26 02:35:08 +08:00
|
|
|
|
clearPageDragState();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
void dispatchTreeCommand(pageRow, {
|
|
|
|
|
|
action: 'move',
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(pageRow),
|
|
|
|
|
|
documentId: sourceNodeId,
|
|
|
|
|
|
parentId: target.parentId,
|
|
|
|
|
|
sortOrder: target.sortOrder
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var _dndRT_ = window.__mnoteFileTreeDndRuntime;
|
|
|
|
|
|
if (_dndRT_ && typeof _dndRT_.handleFileTreeDrop === 'function') {
|
|
|
|
|
|
_dndRT_.handleFileTreeDrop(event, {
|
|
|
|
|
|
closestAction: closestAction,
|
|
|
|
|
|
resolveWorkspaceId: resolveWorkspaceId,
|
|
|
|
|
|
fileTreeRowLocalUploadTargetRelativePath: fileTreeRowLocalUploadTargetRelativePath,
|
|
|
|
|
|
ensureFileTreeWritableTarget: ensureFileTreeWritableTarget,
|
|
|
|
|
|
dispatchSidebarEvent: dispatchSidebarEvent,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// inline fallback
|
|
|
|
|
|
var fileTree = document.getElementById('sidebar-file-tree-root');
|
|
|
|
|
|
if (fileTree && fileTree.contains(event.target)) {
|
|
|
|
|
|
var targetRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]');
|
|
|
|
|
|
var files = event.dataTransfer ? Array.from(event.dataTransfer.files || []) : [];
|
|
|
|
|
|
var raw = event.dataTransfer ? event.dataTransfer.getData(FILETREE_DRAG_MIME) || event.dataTransfer.getData('application/x-mnote-file-tree') || '' : '';
|
|
|
|
|
|
var rowIds = draggingFileTreeRowIds.slice();
|
|
|
|
|
|
if (raw) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var parsed = JSON.parse(raw);
|
|
|
|
|
|
if (Array.isArray(parsed.rowIds)) rowIds = parsed.rowIds;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!files.length && !rowIds.length) return;
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
var detail = {
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(targetRow || fileTree),
|
|
|
|
|
|
targetRowId: targetRow ? targetRow.getAttribute('data-row-id') : null,
|
|
|
|
|
|
targetRowKind: targetRow ? targetRow.getAttribute('data-row-kind') : 'root',
|
|
|
|
|
|
documentId: targetRow ? targetRow.getAttribute('data-document-id') || targetRow.getAttribute('data-doc-id') : null,
|
|
|
|
|
|
assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null,
|
|
|
|
|
|
targetRelativePath: targetRow ? fileTreeRowLocalUploadTargetRelativePath(targetRow) : '',
|
|
|
|
|
|
uploadIntent: 'filetree.folder.drop'
|
|
|
|
|
|
};
|
|
|
|
|
|
if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false');
|
|
|
|
|
|
activeFileTreeDropRow = null;
|
|
|
|
|
|
if (files.length) {
|
|
|
|
|
|
dispatchSidebarEvent('tree.filetree.external-drop', Object.assign({}, detail, { files: files }));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
void Promise.resolve(ensureFileTreeWritableTarget('drop', targetRow, rowIds, event.altKey === true || event.ctrlKey === true || event.metaKey === true)).then(function(writable) {
|
|
|
|
|
|
if (!writable) return;
|
|
|
|
|
|
dispatchSidebarEvent('tree.filetree.internal-drop', Object.assign({}, detail, { rowIds: rowIds, copy: event.altKey === true || event.ctrlKey === true || event.metaKey === true }));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
draggingFileTreeRowIds = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('dragend', function() {
|
2026-05-26 02:35:08 +08:00
|
|
|
|
clearPageDragState();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
var _dndRT_ = window.__mnoteFileTreeDndRuntime;
|
|
|
|
|
|
if (_dndRT_ && typeof _dndRT_.resetFileTreeDragState === 'function') {
|
|
|
|
|
|
_dndRT_.resetFileTreeDragState();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
draggingFileTreeRowIds = [];
|
|
|
|
|
|
clearPageDropFeedback();
|
|
|
|
|
|
if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false');
|
|
|
|
|
|
activeFileTreeDropRow = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-26 01:51:07 +08:00
|
|
|
|
sidebarTreeLiveApply.installTreeLiveApplyEventListeners();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
var tree = document.getElementById('sidebar-tree-root');
|
|
|
|
|
|
var activeId = currentDocumentId();
|
|
|
|
|
|
if (tree && activeId) {
|
|
|
|
|
|
var activeRow = tree.querySelector('.tree-row[data-node-id="' + cssEscape(activeId) + '"]');
|
|
|
|
|
|
if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
var initiallySelectedFileRows = document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-selected="true"]');
|
|
|
|
|
|
initiallySelectedFileRows.forEach(function(row) {
|
|
|
|
|
|
if (!(row instanceof HTMLElement)) return;
|
|
|
|
|
|
var rowId = row.getAttribute('data-row-id') || '';
|
|
|
|
|
|
if (!rowId) return;
|
|
|
|
|
|
sidebarFileTreeSelection.selectedRowIds.add(rowId);
|
|
|
|
|
|
if (!sidebarFileTreeSelection.anchorRowId) sidebarFileTreeSelection.anchorRowId = rowId;
|
|
|
|
|
|
sidebarFileTreeSelection.focusedRowId = rowId;
|
|
|
|
|
|
});
|
|
|
|
|
|
syncSidebarFileTreeSelection();
|
|
|
|
|
|
schedulePendingLocalFolderRestoreFocus();
|
|
|
|
|
|
window.addEventListener('mnote:primary-document-activated', function(event) {
|
|
|
|
|
|
var detail = event && event.detail ? event.detail : {};
|
2026-05-26 16:47:03 +08:00
|
|
|
|
pageUiState.pageOptions = null;
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
2026-05-25 17:36:17 +08:00
|
|
|
|
selectSidebarFileTreeDocument(detail.documentId, { scrollIntoView: false });
|
|
|
|
|
|
});
|
2026-05-26 17:24:05 +08:00
|
|
|
|
window.addEventListener('mnote:page-aggregate-synced', function(event) {
|
|
|
|
|
|
var detail = event && event.detail ? event.detail : {};
|
|
|
|
|
|
if (detail.scriptId && detail.scriptId !== '__MNOTE_PAGE_AGGREGATE__') return;
|
|
|
|
|
|
var documentId = searchText(detail.documentId || '');
|
|
|
|
|
|
if (documentId && currentDocumentId() && documentId !== currentDocumentId()) return;
|
|
|
|
|
|
pageUiState.pageOptions = null;
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
|
|
|
|
|
});
|
2026-05-25 17:36:17 +08:00
|
|
|
|
restoreSidebarTreeTab();
|
|
|
|
|
|
startLocalFolderSidebarWatch();
|
|
|
|
|
|
})();
|