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 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,
|
|
|
|
|
|
historySnapshots: [],
|
|
|
|
|
|
pageSettingsOpen: false,
|
|
|
|
|
|
pageAiOpen: false,
|
|
|
|
|
|
pageAiBusy: false,
|
|
|
|
|
|
pageAiMessages: [],
|
|
|
|
|
|
pageAiSuggestionIndex: 0,
|
|
|
|
|
|
pageAiProvider: 'hermes',
|
|
|
|
|
|
pageAiPage: 'chat',
|
|
|
|
|
|
pageAiRunStatus: 'idle',
|
|
|
|
|
|
pageAiCurrentRunId: '',
|
|
|
|
|
|
pageAiAcpRuntime: 'reasonix',
|
|
|
|
|
|
pageAiAcpRuntimes: [],
|
|
|
|
|
|
pageAiQueueLength: 0,
|
|
|
|
|
|
pageAiQueuedItems: [],
|
|
|
|
|
|
pageAiStoppedRunIds: {},
|
|
|
|
|
|
pageAiAbortController: null,
|
|
|
|
|
|
pageAiContextScope: 'page',
|
|
|
|
|
|
pageAiTools: [],
|
|
|
|
|
|
pageAiToolsError: '',
|
|
|
|
|
|
pageAiGatewayHealth: null,
|
|
|
|
|
|
pageAiGatewayHealthError: '',
|
|
|
|
|
|
pageAiLastToolCall: null,
|
|
|
|
|
|
pageAiProfiles: [],
|
|
|
|
|
|
pageAiActiveProfileName: 'mnoteai',
|
|
|
|
|
|
pageAiProfileError: '',
|
|
|
|
|
|
pageAiProfileMemory: { memory: '', user: '', soul: '' },
|
|
|
|
|
|
pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' },
|
|
|
|
|
|
pageAiProfileMemoryError: '',
|
|
|
|
|
|
pageAiSkills: { categories: [], archived: [] },
|
|
|
|
|
|
pageAiSkillQuery: '',
|
|
|
|
|
|
pageAiSkillError: '',
|
|
|
|
|
|
pageAiSessions: [],
|
|
|
|
|
|
pageAiActiveSessionId: '',
|
|
|
|
|
|
pageAiSessionSearchQuery: '',
|
|
|
|
|
|
pageAiSessionSearchResults: [],
|
|
|
|
|
|
pageAiSessionSearchTimer: 0,
|
|
|
|
|
|
pageAiSessionError: '',
|
|
|
|
|
|
pageAiPermissionRequests: [],
|
|
|
|
|
|
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__') || {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function defaultPageOptions() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
wideLayout: false,
|
|
|
|
|
|
smallText: false,
|
|
|
|
|
|
layoutDensity: 'normal',
|
|
|
|
|
|
showHeadingNumbers: false,
|
|
|
|
|
|
showToc: false,
|
|
|
|
|
|
showStructure: false,
|
|
|
|
|
|
protectEditing: false,
|
|
|
|
|
|
showWordCount: true,
|
|
|
|
|
|
collapseBacklinks: false,
|
|
|
|
|
|
pageFont: 'default',
|
|
|
|
|
|
hideChildPages: false,
|
|
|
|
|
|
showBlockRefCount: false,
|
|
|
|
|
|
hideTitleHeader: false,
|
|
|
|
|
|
embedDefaultBlockId: null
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function currentPageOptions() {
|
|
|
|
|
|
if (pageUiState.pageOptions) return pageUiState.pageOptions;
|
|
|
|
|
|
var aggregate = currentPageAggregate();
|
|
|
|
|
|
var current = aggregate && aggregate.layout && aggregate.layout.pageOptions && typeof aggregate.layout.pageOptions === 'object'
|
|
|
|
|
|
? aggregate.layout.pageOptions
|
|
|
|
|
|
: null;
|
|
|
|
|
|
if (!current) {
|
|
|
|
|
|
return defaultPageOptions();
|
|
|
|
|
|
}
|
|
|
|
|
|
pageUiState.pageOptions = Object.assign(defaultPageOptions(), current);
|
|
|
|
|
|
return pageUiState.pageOptions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function readGlobalShowHeadingNumbers() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var raw = window.localStorage ? window.localStorage.getItem(MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY) : '';
|
|
|
|
|
|
return raw === 'true' || raw === '1';
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function writeGlobalShowHeadingNumbers(value) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (window.localStorage) {
|
|
|
|
|
|
window.localStorage.setItem(MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY, value ? 'true' : 'false');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
document.documentElement.setAttribute('data-global-show-heading-numbers', String(Boolean(value)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function effectiveShowHeadingNumbers(options) {
|
|
|
|
|
|
return readGlobalShowHeadingNumbers();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageOptionIsSupported(name) {
|
|
|
|
|
|
return name === 'wideLayout'
|
|
|
|
|
|
|| name === 'smallText'
|
|
|
|
|
|
|| name === 'layoutDensity'
|
|
|
|
|
|
|| name === 'pageFont'
|
|
|
|
|
|
|| name === 'showHeadingNumbers'
|
|
|
|
|
|
|| (name === 'hideTitleHeader' && currentSourceKind() === 'local_folder');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageOptionDescription(name) {
|
|
|
|
|
|
if (name === 'wideLayout') return '自适应宽度';
|
|
|
|
|
|
if (name === 'smallText') return '小字体';
|
|
|
|
|
|
if (name === 'hideTitleHeader') return '隐藏本地 Markdown 文件标题';
|
|
|
|
|
|
if (name === 'showToc') return '标题目录';
|
|
|
|
|
|
if (name === 'showHeadingNumbers') return '标题自动编号';
|
|
|
|
|
|
if (name === 'protectEditing') return '编辑保护';
|
|
|
|
|
|
if (name === 'collapseBacklinks') return '折叠反向引用';
|
|
|
|
|
|
if (name === 'hideChildPages') return '隐藏子页面';
|
|
|
|
|
|
if (name === 'showBlockRefCount') return '显示块引用数字';
|
|
|
|
|
|
return name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageOptionHint(name) {
|
|
|
|
|
|
if (name === 'wideLayout') return '已接通:主内容列宽度立即变化';
|
|
|
|
|
|
if (name === 'smallText') return '已接通:正文排版会更紧凑';
|
|
|
|
|
|
if (name === 'showHeadingNumbers') return '已接通:标题前显示顺序编号';
|
|
|
|
|
|
if (name === 'hideTitleHeader') return '已接通:本地 Markdown 页头标题立即隐藏或显示';
|
|
|
|
|
|
if (name === 'layoutDensity') return '已接通:段落与列表间距会变化';
|
|
|
|
|
|
if (name === 'pageFont') return '已接通:当前页面字体会切换';
|
|
|
|
|
|
if (name === 'showToc') return '待接线:当前 Rust 壳还没有正式目录面板';
|
|
|
|
|
|
if (name === 'protectEditing') return '待接线:当前主编辑器只显示降级说明';
|
|
|
|
|
|
if (name === 'collapseBacklinks') return '待接线:当前 Rust 壳未挂回链面板';
|
|
|
|
|
|
if (name === 'hideChildPages') return '待接线:当前页面壳还没有子页面块显隐';
|
|
|
|
|
|
if (name === 'showBlockRefCount') return '待接线:当前页面壳未显示块引用计数';
|
|
|
|
|
|
return '待接线';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensureHistorySnapshotsSeeded() {
|
|
|
|
|
|
if (pageUiState.historySnapshots.length > 0) return;
|
|
|
|
|
|
var aggregate = currentPageAggregate();
|
|
|
|
|
|
var stats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
|
|
|
|
|
|
pageUiState.historySnapshots = [{
|
|
|
|
|
|
id: 'snapshot-initial',
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
stats: {
|
|
|
|
|
|
wordCount: Number(stats.wordCount || 0),
|
|
|
|
|
|
characterCount: Number(stats.characterCount || 0),
|
|
|
|
|
|
blockCount: Number(stats.blockCount || 0),
|
|
|
|
|
|
todoTotal: Number(stats.todoTotal || 0),
|
|
|
|
|
|
todoDone: Number(stats.todoDone || 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function computeLivePageStats() {
|
|
|
|
|
|
var aggregate = currentPageAggregate();
|
|
|
|
|
|
var fallbackStats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
|
|
|
|
|
|
var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
|
|
|
|
|
|
if (!(editor instanceof HTMLElement)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
wordCount: Number(fallbackStats.wordCount || 0),
|
|
|
|
|
|
characterCount: Number(fallbackStats.characterCount || 0),
|
|
|
|
|
|
blockCount: Number(fallbackStats.blockCount || 0),
|
|
|
|
|
|
todoTotal: Number(fallbackStats.todoTotal || 0),
|
|
|
|
|
|
todoDone: Number(fallbackStats.todoDone || 0)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
var text = (editor.textContent || '').trim();
|
|
|
|
|
|
var compact = text.replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
var wordCount = compact ? compact.split(' ').filter(Boolean).length : 0;
|
|
|
|
|
|
var characterCount = text.replace(/\s/g, '').length;
|
|
|
|
|
|
var blockCount = editor.querySelectorAll(':scope > *').length;
|
|
|
|
|
|
var todos = Array.from(editor.querySelectorAll('input[type="checkbox"]'));
|
|
|
|
|
|
return {
|
|
|
|
|
|
wordCount: wordCount || Number(fallbackStats.wordCount || 0),
|
|
|
|
|
|
characterCount: characterCount || Number(fallbackStats.characterCount || 0),
|
|
|
|
|
|
blockCount: blockCount || Number(fallbackStats.blockCount || 0),
|
|
|
|
|
|
todoTotal: todos.length || Number(fallbackStats.todoTotal || 0),
|
|
|
|
|
|
todoDone: todos.filter(function(node){ return node.checked; }).length || Number(fallbackStats.todoDone || 0)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function recordPageHistorySnapshot(reason, stats) {
|
|
|
|
|
|
ensureHistorySnapshotsSeeded();
|
|
|
|
|
|
pageUiState.historySnapshots = [{
|
|
|
|
|
|
id: 'snapshot-' + Date.now(),
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
reason: reason || 'save',
|
|
|
|
|
|
stats: stats || computeLivePageStats()
|
|
|
|
|
|
}].concat(pageUiState.historySnapshots).slice(0, 15);
|
|
|
|
|
|
renderPageHistoryDrawer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyPageOptionsToShell() {
|
|
|
|
|
|
var options = currentPageOptions();
|
|
|
|
|
|
var globalShowHeadingNumbers = readGlobalShowHeadingNumbers();
|
|
|
|
|
|
var showHeadingNumbers = effectiveShowHeadingNumbers(options);
|
|
|
|
|
|
var shell = document.querySelector('.document-shell');
|
|
|
|
|
|
var editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
|
|
|
|
|
|
var editorSurface = editorRoot && editorRoot.querySelector('.editor-surface');
|
|
|
|
|
|
if (shell instanceof HTMLElement) {
|
|
|
|
|
|
shell.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
|
|
|
|
shell.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
|
|
|
|
shell.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
|
|
|
|
shell.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
|
|
|
|
shell.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
|
|
|
|
shell.style.width = '100%';
|
|
|
|
|
|
shell.style.maxWidth = options.wideLayout ? '980px' : '760px';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (editorRoot instanceof HTMLElement) {
|
|
|
|
|
|
editorRoot.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
|
|
|
|
editorRoot.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
|
|
|
|
editorRoot.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
|
|
|
|
editorRoot.setAttribute('data-page-embed-default-block-id', options.embedDefaultBlockId == null ? '' : String(options.embedDefaultBlockId));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (editorSurface instanceof HTMLElement) {
|
|
|
|
|
|
editorSurface.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
|
|
|
|
editorSurface.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
|
|
|
|
editorSurface.setAttribute('data-show-heading-numbers', String(showHeadingNumbers));
|
|
|
|
|
|
}
|
|
|
|
|
|
document.documentElement.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
|
|
|
|
document.documentElement.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
|
|
|
|
document.documentElement.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
|
|
|
|
document.documentElement.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
|
|
|
|
document.documentElement.setAttribute('data-global-show-heading-numbers', String(globalShowHeadingNumbers));
|
|
|
|
|
|
document.documentElement.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
|
|
|
|
document.documentElement.setAttribute('data-page-hide-title-header', String(Boolean(options.hideTitleHeader)));
|
|
|
|
|
|
var titleHeader = document.querySelector('.document-pane[data-pane-role="primary"] .document-shell-header') || document.querySelector('.document-shell-header');
|
|
|
|
|
|
if (titleHeader instanceof HTMLElement) {
|
|
|
|
|
|
var hidden = Boolean(options.hideTitleHeader);
|
|
|
|
|
|
titleHeader.hidden = hidden;
|
|
|
|
|
|
titleHeader.setAttribute('data-page-title-hidden', String(hidden));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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 title = window.prompt('新建文件夹', '新建文件夹');
|
|
|
|
|
|
if (!title || !title.trim()) return false;
|
|
|
|
|
|
var result = await dispatchTreeCommand(trigger || document.body, {
|
|
|
|
|
|
action: 'create_folder',
|
|
|
|
|
|
workspaceId: workspaceId,
|
|
|
|
|
|
parentId: effectiveParentId || null,
|
|
|
|
|
|
title: title.trim()
|
|
|
|
|
|
});
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-filetree-folder-created', 'true');
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-filetree-folder-created-id', commandDocumentId(result, result.id || ''));
|
|
|
|
|
|
void refreshLocalFolderSidebarSnapshot();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applySidebarTreeTab(mode, shell) {
|
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 02:35:08 +08:00
|
|
|
|
sidebarPageTree = createSidebarPageTreeRuntime({
|
|
|
|
|
|
activeSidebarTreeMode,
|
|
|
|
|
|
commandDocumentId,
|
|
|
|
|
|
copyWorkspaceSourceParams,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
dispatchTreeCommand,
|
|
|
|
|
|
normalizeSidebarTreeMode,
|
|
|
|
|
|
persistLocalFolderSelfChangeSuppression,
|
|
|
|
|
|
persistSidebarTreeMode,
|
|
|
|
|
|
refreshLocalFolderSidebarSnapshot: (...args) => refreshLocalFolderSidebarSnapshot(...args),
|
|
|
|
|
|
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 commandDocumentId = (...args) => sidebarTreeLiveApply.commandDocumentId(...args);
|
|
|
|
|
|
const applyCreatedDocumentLocally = (...args) => sidebarTreeLiveApply.applyCreatedDocumentLocally(...args);
|
|
|
|
|
|
const localCommandNeedsProjectionRefresh = (...args) => sidebarTreeLiveApply.localCommandNeedsProjectionRefresh(...args);
|
|
|
|
|
|
const applyMoveDocumentDelta = (...args) => sidebarTreeLiveApply.applyMoveDocumentDelta(...args);
|
|
|
|
|
|
const applyRemoveDocumentDelta = (...args) => sidebarTreeLiveApply.applyRemoveDocumentDelta(...args);
|
|
|
|
|
|
const setTreeLiveApplyError = (...args) => sidebarTreeLiveApply.setTreeLiveApplyError(...args);
|
|
|
|
|
|
const objectIdentityAttr = (...args) => sidebarTreeLiveApply.objectIdentityAttr(...args);
|
|
|
|
|
|
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-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-26 01:58:00 +08:00
|
|
|
|
const sidebarFileTreeOpen = createSidebarFileTreeOpenRuntime({
|
|
|
|
|
|
copyWorkspaceSourceParams,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
fileTreeIconKindForFileName: (...args) => fileTreeIconKindForFileName(...args),
|
|
|
|
|
|
getNavigationInFlight: () => mnoteNavigationInFlight,
|
|
|
|
|
|
isCodeAttachmentFileName: (...args) => isCodeAttachmentFileName(...args),
|
|
|
|
|
|
isNonOfficeAttachmentName: (...args) => isNonOfficeAttachmentName(...args),
|
|
|
|
|
|
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);
|
|
|
|
|
|
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,
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
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 index = await fetchLegacyOfficeAttachmentIndex();
|
|
|
|
|
|
var paragraphs = Array.from(editor.querySelectorAll('p'));
|
|
|
|
|
|
paragraphs.forEach(function(paragraph) {
|
|
|
|
|
|
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;
|
|
|
|
|
|
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-mnote-attachment-link', 'true');
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
attachmentMetaPending[assetId] = fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), {
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
cache: 'no-store'
|
|
|
|
|
|
}).then(function(response) {
|
|
|
|
|
|
return response.json().catch(function() { return null; }).then(function(payload) {
|
|
|
|
|
|
if (!response.ok || !payload) return null;
|
|
|
|
|
|
var asset = payload.asset && typeof payload.asset === 'object' ? payload.asset : {};
|
|
|
|
|
|
var meta = {
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
fileSize: uploadedFileSize(asset)
|
|
|
|
|
|
};
|
|
|
|
|
|
attachmentMetaCache[assetId] = meta;
|
|
|
|
|
|
return meta;
|
|
|
|
|
|
});
|
|
|
|
|
|
}).catch(function() {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}).finally(function() {
|
|
|
|
|
|
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,
|
|
|
|
|
|
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);
|
|
|
|
|
|
var url = localAssetOpenUrl(asset, false) || uploadedAssetUrl(asset);
|
|
|
|
|
|
var type = uploadedAssetType(asset);
|
|
|
|
|
|
var assetId = String(asset && asset.id || '').trim();
|
|
|
|
|
|
var sizeLabel = uploadedFileSize(asset);
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (type === 'image' && url) {
|
|
|
|
|
|
return editor.chain().focus().setImage({ src: url, alt: title, title: title }).run() === true;
|
|
|
|
|
|
}
|
|
|
|
|
|
var isLocalAsset = isLocalUploadedAsset(asset);
|
|
|
|
|
|
var userId = '';
|
|
|
|
|
|
var onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
|
|
|
|
|
|
if (onlyOfficeUrl && assetId) {
|
|
|
|
|
|
userId = await fetchCurrentOnlyOfficeUserId();
|
|
|
|
|
|
onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
|
|
|
|
|
|
}
|
|
|
|
|
|
var href = onlyOfficeUrl || url;
|
|
|
|
|
|
if (href) {
|
|
|
|
|
|
var storedHref = onlyOfficeUrl
|
|
|
|
|
|
? (isLocalAsset ? onlyOfficeUrl : buildOnlyOfficeOpenPath({
|
|
|
|
|
|
fileUrl: '',
|
|
|
|
|
|
fileName: title,
|
|
|
|
|
|
fileType: inferOnlyOfficeFileType(title, asset && asset.mime_type) || 'docx',
|
|
|
|
|
|
assetId: assetId,
|
|
|
|
|
|
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
|
|
|
|
|
|
userId: userId || '',
|
|
|
|
|
|
mode: 'view'
|
|
|
|
|
|
}))
|
|
|
|
|
|
: href;
|
|
|
|
|
|
var inserted = editor.chain().focus().insertContent([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'paragraph',
|
|
|
|
|
|
content: [{
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
text: title,
|
|
|
|
|
|
marks: [{
|
|
|
|
|
|
type: 'link',
|
|
|
|
|
|
attrs: {
|
|
|
|
|
|
href: storedHref,
|
|
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
window.setTimeout(function() {
|
|
|
|
|
|
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);
|
|
|
|
|
|
if (link instanceof HTMLElement) {
|
|
|
|
|
|
link.setAttribute('data-mnote-attachment-link', 'true');
|
|
|
|
|
|
if (assetId) link.setAttribute('data-asset-id', assetId);
|
|
|
|
|
|
if (sizeLabel) link.setAttribute('data-file-size', sizeLabel);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
void refreshLocalFolderSidebarSnapshot();
|
|
|
|
|
|
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 {
|
|
|
|
|
|
var form = new FormData();
|
|
|
|
|
|
form.append('file', file);
|
|
|
|
|
|
form.append('workspaceId', plan.workspaceId);
|
|
|
|
|
|
form.append('documentId', plan.targetDocumentId);
|
|
|
|
|
|
if (plan.targetMindmapId) form.append('mindmapId', plan.targetMindmapId);
|
|
|
|
|
|
var response = await fetchWithTimeout('/api/media/upload', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
body: form
|
|
|
|
|
|
}, 15000, '上传');
|
|
|
|
|
|
payload = await response.json().catch(function() { return null; });
|
|
|
|
|
|
if (!response.ok || !payload || !payload.asset) {
|
|
|
|
|
|
throw new Error(payload && payload.error ? payload.error : '上传失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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 || {});
|
|
|
|
|
|
});
|
|
|
|
|
|
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,
|
|
|
|
|
|
currentSourceKind,
|
|
|
|
|
|
deleteSingleFileTreeAsset,
|
|
|
|
|
|
dispatchSidebarEvent,
|
|
|
|
|
|
dispatchTreeCommand,
|
|
|
|
|
|
ensureFileTreeWritableTarget,
|
|
|
|
|
|
fileTreeRuntimeDeps,
|
|
|
|
|
|
fileTreeRuntimeFunction,
|
|
|
|
|
|
fileTreeSelectionRuntimeDeps,
|
|
|
|
|
|
fileTreeSelectionRuntimeFunction,
|
|
|
|
|
|
isFileTreePageRow,
|
|
|
|
|
|
localFilePathFromAssetId,
|
|
|
|
|
|
normalizeFileTreePageRenameTitle,
|
|
|
|
|
|
openConvexAssetFromFileTree,
|
|
|
|
|
|
openEditorAttachmentDetail,
|
|
|
|
|
|
openEditorAttachmentDownload,
|
|
|
|
|
|
openEditorAttachmentEditTab,
|
|
|
|
|
|
openEditorAttachmentNewWindow,
|
|
|
|
|
|
refreshLocalFolderSidebarSnapshot,
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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>' +
|
|
|
|
|
|
'<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">' +
|
|
|
|
|
|
'<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>' +
|
|
|
|
|
|
'</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"]');
|
|
|
|
|
|
if (input) input.addEventListener('input', scheduleSearchResultsRender);
|
|
|
|
|
|
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
|
|
|
|
|
button.addEventListener('click', function() {
|
|
|
|
|
|
var isOn = button.getAttribute('aria-checked') !== 'true';
|
|
|
|
|
|
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
|
|
|
|
|
button.classList.toggle('is-on', isOn);
|
|
|
|
|
|
scheduleSearchResultsRender();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
if (closeButton) closeButton.addEventListener('click', closeSearchModal);
|
|
|
|
|
|
overlay.addEventListener('click', function(event) {
|
|
|
|
|
|
if (event.target === overlay) closeSearchModal();
|
|
|
|
|
|
});
|
|
|
|
|
|
return overlay;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var activeSearchRequestId = 0;
|
|
|
|
|
|
var searchRenderTimer = 0;
|
|
|
|
|
|
|
|
|
|
|
|
function searchText(value) {
|
|
|
|
|
|
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function searchSwitchValue(overlay, name) {
|
|
|
|
|
|
var button = overlay.querySelector('[data-search-switch="' + name + '"]');
|
|
|
|
|
|
return button instanceof HTMLElement && button.getAttribute('aria-checked') === 'true';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function highlightedHtml(value) {
|
|
|
|
|
|
return escapeHtml(value)
|
|
|
|
|
|
.replace(/<mark>/g, '<mark>')
|
|
|
|
|
|
.replace(/<\/mark>/g, '</mark>');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function highlightSearchTitle(title, query) {
|
|
|
|
|
|
var cleanTitle = searchText(title);
|
|
|
|
|
|
var cleanQuery = searchText(query);
|
|
|
|
|
|
if (!cleanQuery) return escapeHtml(cleanTitle);
|
|
|
|
|
|
var index = cleanTitle.toLowerCase().indexOf(cleanQuery.toLowerCase());
|
|
|
|
|
|
if (index < 0) return escapeHtml(cleanTitle);
|
|
|
|
|
|
return escapeHtml(cleanTitle.slice(0, index)) +
|
|
|
|
|
|
'<mark>' + escapeHtml(cleanTitle.slice(index, index + cleanQuery.length)) + '</mark>' +
|
|
|
|
|
|
escapeHtml(cleanTitle.slice(index + cleanQuery.length));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
var requestId = ++activeSearchRequestId;
|
|
|
|
|
|
meta.innerHTML = '<span>搜索中…</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-loading="true">搜索中…</div>';
|
|
|
|
|
|
try {
|
|
|
|
|
|
var response = await fetch('/api/search/documents', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
|
|
|
|
sourceKind: currentSourceKind() || null,
|
|
|
|
|
|
rootUri: currentRootUri() || null,
|
|
|
|
|
|
documentId: currentDocumentId() || null,
|
|
|
|
|
|
query: query,
|
|
|
|
|
|
limit: 30,
|
|
|
|
|
|
filters: {
|
|
|
|
|
|
titleOnly: searchSwitchValue(overlay, 'title'),
|
|
|
|
|
|
exact: searchSwitchValue(overlay, 'exact'),
|
|
|
|
|
|
includeOcr: false,
|
|
|
|
|
|
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 : [];
|
|
|
|
|
|
meta.innerHTML = '<span>共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
|
|
|
|
if (!items.length) {
|
|
|
|
|
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
results.innerHTML = items.map(function(item) {
|
|
|
|
|
|
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
|
|
|
|
|
var snippet = searchText(item.snippet || (Array.isArray(item.evidence) && item.evidence[0] && item.evidence[0].snippet) || '');
|
|
|
|
|
|
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
|
|
|
|
|
var resourceType = searchText(item.resourceType || item.resourceKind || 'page');
|
|
|
|
|
|
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '">' +
|
|
|
|
|
|
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
|
|
|
|
|
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchTitle(title, query) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-search-result-snippet">' + (snippet ? highlightedHtml(snippet) : escapeHtml(path)) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-search-result-path"><span>' + escapeHtml(path) + '</span><span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span></span></span>' +
|
|
|
|
|
|
'</button>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
} 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>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
|
|
|
|
|
if (input) input.setAttribute('placeholder', '在 ' + currentWorkspaceName() + ' 中搜索');
|
|
|
|
|
|
void renderSearchResults();
|
|
|
|
|
|
if (input) {
|
|
|
|
|
|
setTimeout(function() { input.focus(); input.select(); }, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSearchModal() {
|
|
|
|
|
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
|
|
|
|
|
if (overlay instanceof HTMLElement) overlay.hidden = true;
|
|
|
|
|
|
document.documentElement.removeAttribute('data-mnote-search-modal-open');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleSearchModal() {
|
|
|
|
|
|
if (isSearchModalOpen()) closeSearchModal();
|
|
|
|
|
|
else openSearchModal();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updatePageSettingsTriggerState() {
|
|
|
|
|
|
var trigger = document.querySelector('[data-testid="wolai-page-settings-trigger"]');
|
|
|
|
|
|
if (!(trigger instanceof HTMLElement)) return;
|
|
|
|
|
|
trigger.setAttribute('data-state', pageUiState.pageSettingsOpen ? 'open' : 'closed');
|
|
|
|
|
|
trigger.setAttribute('aria-expanded', pageUiState.pageSettingsOpen ? 'true' : 'false');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createPageOptionRow(key, type) {
|
|
|
|
|
|
var inputType = type || 'checkbox';
|
|
|
|
|
|
var supported = pageOptionIsSupported(key);
|
|
|
|
|
|
if (inputType === 'checkbox') {
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<label class="wolai-page-setting-row' + (supported ? '' : ' is-pending') + '" data-page-setting-row="' + key + '">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-copy">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
|
|
|
|
|
|
'</span>' +
|
|
|
|
|
|
'<input type="checkbox" class="wolai-page-setting-checkbox" data-page-option-checkbox="' + key + '"' + (supported ? '' : ' data-setting-pending="true"') + ' />' +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="' + key + '">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-copy">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
|
|
|
|
|
|
'</span>' +
|
|
|
|
|
|
'<select class="wolai-page-setting-select" data-page-option-select="' + key + '">' +
|
|
|
|
|
|
'<option value="compact">紧凑</option>' +
|
|
|
|
|
|
'<option value="normal">默认</option>' +
|
|
|
|
|
|
'<option value="spacious">宽松</option>' +
|
|
|
|
|
|
'</select>' +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createGlobalHeadingNumbersRow() {
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="globalShowHeadingNumbers">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-copy">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-label">标题自动编号</span>' +
|
|
|
|
|
|
'</span>' +
|
|
|
|
|
|
'<input type="checkbox" class="wolai-page-setting-checkbox" data-global-option-checkbox="showHeadingNumbers" />' +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderGlobalOptions(popover) {
|
|
|
|
|
|
var globalHeadingNumbers = readGlobalShowHeadingNumbers();
|
|
|
|
|
|
popover.querySelectorAll('[data-global-option-checkbox="showHeadingNumbers"]').forEach(function(input) {
|
|
|
|
|
|
input.checked = globalHeadingNumbers;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createPageFontRow() {
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="pageFont">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-copy">' +
|
|
|
|
|
|
'<span class="wolai-page-setting-label">页面字体</span>' +
|
|
|
|
|
|
'<span class="wolai-page-setting-hint">已接通:仅对当前页面生效</span>' +
|
|
|
|
|
|
'</span>' +
|
|
|
|
|
|
'<select class="wolai-page-setting-select" data-page-option-select="pageFont">' +
|
|
|
|
|
|
'<option value="default">默认</option>' +
|
|
|
|
|
|
'<option value="song">宋体</option>' +
|
|
|
|
|
|
'<option value="kai">楷体</option>' +
|
|
|
|
|
|
'</select>' +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensurePageHistoryDrawer() {
|
|
|
|
|
|
var existing = document.querySelector('[data-testid="wolai-page-history-drawer"]');
|
|
|
|
|
|
if (existing instanceof HTMLElement) return existing;
|
|
|
|
|
|
var drawer = document.createElement('aside');
|
|
|
|
|
|
drawer.className = 'wolai-page-history-drawer';
|
|
|
|
|
|
drawer.setAttribute('data-testid', 'wolai-page-history-drawer');
|
|
|
|
|
|
drawer.setAttribute('data-mnote-surface', 'page-history');
|
|
|
|
|
|
drawer.hidden = true;
|
|
|
|
|
|
drawer.innerHTML = '' +
|
|
|
|
|
|
'<div class="wolai-page-history-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-history-header">' +
|
|
|
|
|
|
'<div><div class="wolai-page-history-title">页面历史</div><div class="wolai-page-history-subtitle">当前会话内最近保存的 15 个快照。</div></div>' +
|
|
|
|
|
|
'<button type="button" class="wolai-surface-close" data-page-history-action="close" aria-label="关闭页面历史">×</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-history-list" data-page-history-list></div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
drawer.addEventListener('click', function(event) {
|
|
|
|
|
|
if (event.target === drawer) closePageHistoryDrawer();
|
|
|
|
|
|
});
|
|
|
|
|
|
document.body.appendChild(drawer);
|
|
|
|
|
|
return drawer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderPageHistoryDrawer() {
|
|
|
|
|
|
var drawer = ensurePageHistoryDrawer();
|
|
|
|
|
|
var list = drawer.querySelector('[data-page-history-list]');
|
|
|
|
|
|
if (!(list instanceof HTMLElement)) return;
|
|
|
|
|
|
ensureHistorySnapshotsSeeded();
|
|
|
|
|
|
list.innerHTML = pageUiState.historySnapshots.length
|
|
|
|
|
|
? pageUiState.historySnapshots.map(function(snapshot) {
|
|
|
|
|
|
var stats = snapshot.stats || {};
|
|
|
|
|
|
var label = new Date(snapshot.timestamp).toLocaleString('zh-CN', {
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
|
});
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<div class="wolai-page-history-item">' +
|
|
|
|
|
|
'<div class="wolai-page-history-item-copy">' +
|
|
|
|
|
|
'<div class="wolai-page-history-item-title">' + escapeHtml(label) + '</div>' +
|
|
|
|
|
|
'<div class="wolai-page-history-item-meta">字数 ' + Number(stats.wordCount || 0) + ' · 字符 ' + Number(stats.characterCount || 0) + ' · 块数 ' + Number(stats.blockCount || 0) + '</div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-history-item-ghost" data-page-history-action="noop">仅查看</button>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}).join('')
|
|
|
|
|
|
: '<div class="wolai-page-history-empty">尚未产生历史快照,编辑后会自动生成。</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openPageHistoryDrawer() {
|
|
|
|
|
|
renderPageHistoryDrawer();
|
|
|
|
|
|
var drawer = ensurePageHistoryDrawer();
|
|
|
|
|
|
drawer.hidden = false;
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-page-history-open', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closePageHistoryDrawer() {
|
|
|
|
|
|
var drawer = document.querySelector('[data-testid="wolai-page-history-drawer"]');
|
|
|
|
|
|
if (drawer instanceof HTMLElement) drawer.hidden = true;
|
|
|
|
|
|
document.documentElement.removeAttribute('data-mnote-page-history-open');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensurePageShareDialog() {
|
|
|
|
|
|
var existing = document.querySelector('[data-testid="wolai-page-share-dialog"]');
|
|
|
|
|
|
if (existing instanceof HTMLElement) return existing;
|
|
|
|
|
|
var dialog = document.createElement('div');
|
|
|
|
|
|
dialog.className = 'wolai-page-share-dialog';
|
|
|
|
|
|
dialog.setAttribute('data-testid', 'wolai-page-share-dialog');
|
|
|
|
|
|
dialog.setAttribute('data-mnote-surface', 'page-share');
|
|
|
|
|
|
dialog.hidden = true;
|
|
|
|
|
|
dialog.innerHTML = '' +
|
|
|
|
|
|
'<div class="wolai-page-share-card" role="dialog" aria-modal="true">' +
|
|
|
|
|
|
'<div class="wolai-page-share-header">' +
|
|
|
|
|
|
'<div><div class="wolai-page-share-title">公开分享页面</div><div class="wolai-page-share-subtitle">当前 3000 公开入口由 mnote-web 持有。</div></div>' +
|
|
|
|
|
|
'<button type="button" class="wolai-surface-close" data-page-share-action="close" aria-label="关闭公开分享页面">×</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-share-state">' +
|
|
|
|
|
|
'<span class="wolai-public-pill wolai-public-pill--inline">全网公开</span>' +
|
|
|
|
|
|
'<span class="wolai-page-share-copy">任何拥有链接的人都可以访问当前页面。</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-share-url-row">' +
|
|
|
|
|
|
'<input class="wolai-page-share-url" type="text" readonly data-page-share-url value="" />' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-share-copy-button" data-page-share-action="copy-link">复制链接</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-share-footer">更多共享者、群组公开和权限策略仍待接线。</div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
dialog.addEventListener('click', function(event) {
|
|
|
|
|
|
if (event.target === dialog) closePageShareDialog();
|
|
|
|
|
|
});
|
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
|
return dialog;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openPageShareDialog() {
|
|
|
|
|
|
var dialog = ensurePageShareDialog();
|
|
|
|
|
|
var input = dialog.querySelector('[data-page-share-url]');
|
|
|
|
|
|
if (input instanceof HTMLInputElement) input.value = window.location.href;
|
|
|
|
|
|
dialog.hidden = false;
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-page-share-open', 'true');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closePageShareDialog() {
|
|
|
|
|
|
var dialog = document.querySelector('[data-testid="wolai-page-share-dialog"]');
|
|
|
|
|
|
if (dialog instanceof HTMLElement) dialog.hidden = true;
|
|
|
|
|
|
document.documentElement.removeAttribute('data-mnote-page-share-open');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
const pageAiSetContextScope = (...args) => sidebarPageAi.pageAiSetContextScope(...args);
|
|
|
|
|
|
const updatePageAiTriggerState = (...args) => sidebarPageAi.updatePageAiTriggerState(...args);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
function ensurePageSettingsPopover() {
|
|
|
|
|
|
var existing = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
|
|
|
|
if (existing instanceof HTMLElement) return existing;
|
|
|
|
|
|
var popover = document.createElement('div');
|
|
|
|
|
|
popover.className = 'wolai-page-settings-popover';
|
|
|
|
|
|
popover.setAttribute('data-testid', 'wolai-page-settings-popover');
|
|
|
|
|
|
popover.setAttribute('data-mnote-surface', 'page-settings');
|
|
|
|
|
|
popover.hidden = true;
|
|
|
|
|
|
popover.innerHTML = '' +
|
|
|
|
|
|
'<div class="wolai-page-settings-panel" role="dialog" aria-modal="false">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-tabs" data-testid="wolai-page-settings-tabs" role="tablist" aria-label="页面设置分组">' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-tab is-active" role="tab" aria-selected="true" data-page-settings-tab="page">页面选项</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="custom">自定义页面</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="index">索引</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="global">全局选项</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="page">' +
|
|
|
|
|
|
createPageOptionRow('wideLayout', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('smallText', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('showToc', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('protectEditing', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('hideTitleHeader', 'checkbox') +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="custom" hidden>' +
|
|
|
|
|
|
createPageFontRow() +
|
|
|
|
|
|
createPageOptionRow('layoutDensity', 'select') +
|
|
|
|
|
|
createPageOptionRow('collapseBacklinks', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('hideChildPages', 'checkbox') +
|
|
|
|
|
|
createPageOptionRow('showBlockRefCount', 'checkbox') +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="index" hidden>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-panel" data-testid="wolai-page-settings-local-index-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-status" data-testid="wolai-page-settings-local-index-status"></div>' +
|
|
|
|
|
|
'<section class="wolai-page-settings-index-group">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-title">反链</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-list" data-testid="wolai-page-settings-local-index-backlinks"></div>' +
|
|
|
|
|
|
'</section>' +
|
|
|
|
|
|
'<section class="wolai-page-settings-index-group">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-title">标签</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-list" data-testid="wolai-page-settings-local-index-tags"></div>' +
|
|
|
|
|
|
'</section>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="global" hidden>' +
|
|
|
|
|
|
createGlobalHeadingNumbersRow() +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-actions">' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="history">页面历史...</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="share">公开分享页面...</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="move" disabled>移动到...</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="embed" disabled>嵌入到...</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-settings-action is-danger is-disabled" data-page-settings-action="delete" disabled>删除页面</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-stats" data-testid="wolai-page-settings-stats"></div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
document.body.appendChild(popover);
|
|
|
|
|
|
return popover;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageSettingsLocalIndexScopeKey() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
currentSourceKind(),
|
|
|
|
|
|
currentRootUri(),
|
|
|
|
|
|
resolveWorkspaceId(document.body),
|
|
|
|
|
|
currentDocumentId()
|
|
|
|
|
|
].join('|');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageSettingsLocalIndexIsAvailable() {
|
|
|
|
|
|
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri()) && Boolean(currentDocumentId());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageSettingsLocalIndexEmpty(message) {
|
|
|
|
|
|
return '<div class="wolai-page-settings-index-empty">' + escapeHtml(message) + '</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderPageSettingsLocalIndexList(items, kind) {
|
|
|
|
|
|
var rows = Array.isArray(items) ? items : [];
|
|
|
|
|
|
if (!rows.length) {
|
|
|
|
|
|
return pageSettingsLocalIndexEmpty(kind === 'backlinks' ? '暂无反链' : '暂无标签');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (kind === 'backlinks') {
|
|
|
|
|
|
return rows.slice(0, 12).map(function(item) {
|
|
|
|
|
|
var title = searchText(item && item.title) || searchText(item && item.path) || '未命名页面';
|
|
|
|
|
|
var path = searchText(item && item.path);
|
|
|
|
|
|
var snippet = searchText(item && item.snippet);
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-row">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-row-title">' + escapeHtml(title) + '</div>' +
|
|
|
|
|
|
(path ? '<div class="wolai-page-settings-index-row-meta">' + escapeHtml(path) + '</div>' : '') +
|
|
|
|
|
|
(snippet ? '<div class="wolai-page-settings-index-row-snippet">' + escapeHtml(snippet) + '</div>' : '') +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
return rows.slice(0, 16).map(function(item) {
|
|
|
|
|
|
var tag = searchText(item && item.tag) || 'untagged';
|
|
|
|
|
|
var count = Number(item && item.count || 0);
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-row is-tag">' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-row-title">#' + escapeHtml(tag) + '</div>' +
|
|
|
|
|
|
'<div class="wolai-page-settings-index-row-meta">' + count + ' 个页面</div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderPageSettingsLocalIndex(popover) {
|
|
|
|
|
|
popover = popover || ensurePageSettingsPopover();
|
|
|
|
|
|
var statusNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
|
|
|
|
|
|
var backlinksNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-backlinks"]');
|
|
|
|
|
|
var tagsNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-tags"]');
|
|
|
|
|
|
if (!(statusNode instanceof HTMLElement) || !(backlinksNode instanceof HTMLElement) || !(tagsNode instanceof HTMLElement)) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!pageSettingsLocalIndexIsAvailable()) {
|
|
|
|
|
|
statusNode.textContent = '本地索引仅在本地工作区页面可用';
|
|
|
|
|
|
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('未连接本地 root');
|
|
|
|
|
|
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('未连接本地 root');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var summary = pageUiState.localIndexSummary || {};
|
|
|
|
|
|
if (summary.loading) {
|
|
|
|
|
|
statusNode.textContent = '正在读取本地索引...';
|
|
|
|
|
|
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('加载中');
|
|
|
|
|
|
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('加载中');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (summary.error) {
|
|
|
|
|
|
statusNode.textContent = '本地索引读取失败';
|
|
|
|
|
|
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty(summary.error);
|
|
|
|
|
|
tagsNode.innerHTML = pageSettingsLocalIndexEmpty(summary.error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (summary.scopeKey !== pageSettingsLocalIndexScopeKey()) {
|
|
|
|
|
|
statusNode.textContent = '切换到索引页签后读取本地索引';
|
|
|
|
|
|
backlinksNode.innerHTML = pageSettingsLocalIndexEmpty('等待读取');
|
|
|
|
|
|
tagsNode.innerHTML = pageSettingsLocalIndexEmpty('等待读取');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
statusNode.textContent = '来自当前授权 root 的 .mnote/index/search-index.json';
|
|
|
|
|
|
backlinksNode.innerHTML = renderPageSettingsLocalIndexList(summary.backlinks, 'backlinks');
|
|
|
|
|
|
tagsNode.innerHTML = renderPageSettingsLocalIndexList(summary.tags, 'tags');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadPageSettingsLocalIndex(force) {
|
|
|
|
|
|
if (!pageSettingsLocalIndexIsAvailable()) {
|
|
|
|
|
|
renderPageSettingsLocalIndex();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var scopeKey = pageSettingsLocalIndexScopeKey();
|
|
|
|
|
|
var current = pageUiState.localIndexSummary || {};
|
|
|
|
|
|
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
|
|
|
|
|
|
renderPageSettingsLocalIndex();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
pageUiState.localIndexSummary = {
|
|
|
|
|
|
scopeKey: scopeKey,
|
|
|
|
|
|
loading: true,
|
|
|
|
|
|
error: '',
|
|
|
|
|
|
backlinks: null,
|
|
|
|
|
|
tags: null
|
|
|
|
|
|
};
|
|
|
|
|
|
renderPageSettingsLocalIndex();
|
|
|
|
|
|
try {
|
|
|
|
|
|
var baseParams = new URLSearchParams();
|
|
|
|
|
|
baseParams.set('workspaceId', resolveWorkspaceId(document.body));
|
|
|
|
|
|
baseParams.set('rootUri', currentRootUri());
|
|
|
|
|
|
var backlinksParams = new URLSearchParams(baseParams);
|
|
|
|
|
|
backlinksParams.set('documentId', currentDocumentId());
|
|
|
|
|
|
var backlinksUrl = '/api/search/local-index/backlinks?' + backlinksParams.toString();
|
|
|
|
|
|
var tagsUrl = '/api/search/local-index/tags?' + baseParams.toString();
|
|
|
|
|
|
var responses = await Promise.all([
|
|
|
|
|
|
fetch(backlinksUrl, { headers: { accept: 'application/json' } }),
|
|
|
|
|
|
fetch(tagsUrl, { headers: { accept: 'application/json' } })
|
|
|
|
|
|
]);
|
|
|
|
|
|
var backlinksPayload = await responses[0].json().catch(function(){ return null; });
|
|
|
|
|
|
var tagsPayload = await responses[1].json().catch(function(){ return null; });
|
|
|
|
|
|
if (!responses[0].ok || !backlinksPayload || backlinksPayload.ok !== true) {
|
|
|
|
|
|
throw new Error('backlinks_' + responses[0].status);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!responses[1].ok || !tagsPayload || tagsPayload.ok !== true) {
|
|
|
|
|
|
throw new Error('tags_' + responses[1].status);
|
|
|
|
|
|
}
|
|
|
|
|
|
pageUiState.localIndexSummary = {
|
|
|
|
|
|
scopeKey: scopeKey,
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
error: '',
|
|
|
|
|
|
backlinks: backlinksPayload.result && Array.isArray(backlinksPayload.result.backlinks) ? backlinksPayload.result.backlinks : [],
|
|
|
|
|
|
tags: tagsPayload.result && Array.isArray(tagsPayload.result.tags) ? tagsPayload.result.tags : []
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
pageUiState.localIndexSummary = {
|
|
|
|
|
|
scopeKey: scopeKey,
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
|
backlinks: [],
|
|
|
|
|
|
tags: []
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
renderPageSettingsLocalIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderPageSettingsPopover() {
|
|
|
|
|
|
var popover = ensurePageSettingsPopover();
|
|
|
|
|
|
var options = currentPageOptions();
|
|
|
|
|
|
popover.querySelectorAll('[data-page-option-checkbox]').forEach(function(input) {
|
|
|
|
|
|
var key = input.getAttribute('data-page-option-checkbox');
|
|
|
|
|
|
input.checked = Boolean(options[key]);
|
|
|
|
|
|
input.disabled = !pageOptionIsSupported(key);
|
|
|
|
|
|
});
|
|
|
|
|
|
popover.querySelectorAll('[data-page-option-select]').forEach(function(select) {
|
|
|
|
|
|
var key = select.getAttribute('data-page-option-select');
|
|
|
|
|
|
var value = key === 'layoutDensity' ? String(options.layoutDensity || 'normal') : String(options.pageFont || 'default');
|
|
|
|
|
|
select.value = value;
|
|
|
|
|
|
});
|
|
|
|
|
|
renderGlobalOptions(popover);
|
|
|
|
|
|
var statsNode = popover.querySelector('[data-testid="wolai-page-settings-stats"]');
|
|
|
|
|
|
if (statsNode instanceof HTMLElement) {
|
|
|
|
|
|
var stats = computeLivePageStats();
|
|
|
|
|
|
statsNode.innerHTML = '' +
|
|
|
|
|
|
'<span>字数 ' + Number(stats.wordCount || 0) + '</span>' +
|
|
|
|
|
|
'<span>字符 ' + Number(stats.characterCount || 0) + '</span>' +
|
|
|
|
|
|
'<span>块数 ' + Number(stats.blockCount || 0) + '</span>' +
|
|
|
|
|
|
'<span>待办 ' + Number(stats.todoDone || 0) + '/' + Number(stats.todoTotal || 0) + '</span>';
|
|
|
|
|
|
}
|
|
|
|
|
|
renderPageSettingsLocalIndex(popover);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setActivePageSettingsTab(tabName) {
|
|
|
|
|
|
var popover = ensurePageSettingsPopover();
|
|
|
|
|
|
popover.querySelectorAll('[data-page-settings-tab]').forEach(function(tab) {
|
|
|
|
|
|
var active = tab.getAttribute('data-page-settings-tab') === tabName;
|
|
|
|
|
|
tab.classList.toggle('is-active', active);
|
|
|
|
|
|
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
|
|
|
|
});
|
|
|
|
|
|
popover.querySelectorAll('[data-page-settings-panel]').forEach(function(panel) {
|
|
|
|
|
|
panel.hidden = panel.getAttribute('data-page-settings-panel') !== tabName;
|
|
|
|
|
|
});
|
|
|
|
|
|
if (tabName === 'index') void loadPageSettingsLocalIndex(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function persistPageOptionsPatch(patch) {
|
|
|
|
|
|
var previous = Object.assign({}, currentPageOptions());
|
|
|
|
|
|
pageUiState.pageOptions = Object.assign({}, previous, patch);
|
|
|
|
|
|
var nextOptions = Object.assign({}, pageUiState.pageOptions);
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
renderPageSettingsPopover();
|
|
|
|
|
|
try {
|
|
|
|
|
|
var response = await fetch('/api/documents/options', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
documentId: currentDocumentId(),
|
|
|
|
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
|
|
|
|
...currentWorkspaceSourcePayload(),
|
|
|
|
|
|
options: nextOptions,
|
|
|
|
|
|
commandName: 'page.layout.updateOptions'
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
|
|
|
|
if (!response.ok || !payload || payload.ok !== true) {
|
|
|
|
|
|
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'page_settings_save_failed_' + response.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-page-options-saved', 'true');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
pageUiState.pageOptions = previous;
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
renderPageSettingsPopover();
|
|
|
|
|
|
document.documentElement.setAttribute('data-mnote-page-options-error', error instanceof Error ? error.message : String(error));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isPageSettingsOpen() {
|
|
|
|
|
|
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
|
|
|
|
return popover instanceof HTMLElement && !popover.hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openPageSettingsPopover() {
|
|
|
|
|
|
if (!currentDocumentId()) return;
|
|
|
|
|
|
var popover = ensurePageSettingsPopover();
|
|
|
|
|
|
renderPageSettingsPopover();
|
|
|
|
|
|
setActivePageSettingsTab('page');
|
|
|
|
|
|
popover.hidden = false;
|
|
|
|
|
|
pageUiState.pageSettingsOpen = true;
|
|
|
|
|
|
updatePageSettingsTriggerState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closePageSettingsPopover() {
|
|
|
|
|
|
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
|
|
|
|
if (popover instanceof HTMLElement) popover.hidden = true;
|
|
|
|
|
|
pageUiState.pageSettingsOpen = false;
|
|
|
|
|
|
updatePageSettingsTriggerState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function togglePageSettingsPopover() {
|
|
|
|
|
|
if (isPageSettingsOpen()) closePageSettingsPopover();
|
|
|
|
|
|
else openPageSettingsPopover();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:05:36 +08:00
|
|
|
|
const sidebarAttachmentOpen = createSidebarAttachmentOpenRuntime({
|
|
|
|
|
|
attachmentClassForFileName: (...args) => attachmentClassForFileName(...args),
|
|
|
|
|
|
buildLocalFileOpenUrl,
|
|
|
|
|
|
buildLocalOnlyOfficeOpenUrl,
|
|
|
|
|
|
buildOnlyOfficeOpenPath,
|
|
|
|
|
|
buildOnlyOfficeOpenUrl,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
currentRootUri,
|
|
|
|
|
|
currentWorkspaceSourcePayload,
|
|
|
|
|
|
fileTreeIconKindForFileName,
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var editorAttachmentLink = closestAction(e.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageSettingsPanel = closestAction(e.target, '.wolai-page-settings-panel');
|
|
|
|
|
|
if (isPageSettingsOpen() && !pageSettingsPanel) {
|
|
|
|
|
|
closePageSettingsPopover();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageSettingsTab = closestAction(e.target, '[data-page-settings-tab]');
|
|
|
|
|
|
if (pageSettingsTab) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setActivePageSettingsTab(pageSettingsTab.getAttribute('data-page-settings-tab') || 'page');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiClose = closestAction(e.target, '[data-page-ai-action="close"]');
|
|
|
|
|
|
if (pageAiClose) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
closePageAiDrawer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSettings = closestAction(e.target, '[data-page-ai-action="open-hermes-settings"]');
|
|
|
|
|
|
if (pageAiSettings) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageAiOpenHermesSettings();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiStop = closestAction(e.target, '[data-page-ai-action="stop-run"]');
|
|
|
|
|
|
if (pageAiStop) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiStopRun();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiRotate = closestAction(e.target, '[data-page-ai-action="rotate"]');
|
|
|
|
|
|
if (pageAiRotate) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageUiState.pageAiSuggestionIndex += 1;
|
|
|
|
|
|
renderPageAiSuggestions();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiIntent = closestAction(e.target, '[data-page-ai-intent]');
|
|
|
|
|
|
if (pageAiIntent) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || '';
|
|
|
|
|
|
if (intentName === 'create-summary') {
|
|
|
|
|
|
void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (intentName === 'create-ai-note') {
|
|
|
|
|
|
void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiTab = closestAction(e.target, '[data-page-ai-tab]');
|
|
|
|
|
|
if (pageAiTab) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat';
|
|
|
|
|
|
if (pageUiState.pageAiPage === 'runtime') void pageAiLoadGatewayHealth();
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
renderPageAiConversation();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiProvider = closestAction(e.target, '[data-page-ai-provider]');
|
|
|
|
|
|
if (pageAiProvider) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageUiState.pageAiProvider = pageAiProvider.getAttribute('data-page-ai-provider') || 'hermes';
|
|
|
|
|
|
renderPageAiProviderButtons();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiMemorySave = closestAction(e.target, '[data-page-ai-memory-save]');
|
|
|
|
|
|
if (pageAiMemorySave) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiSaveProfileMemory(pageAiMemorySave.getAttribute('data-page-ai-memory-save') || '');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSkillToggle = closestAction(e.target, '[data-page-ai-skill-toggle]');
|
|
|
|
|
|
if (pageAiSkillToggle) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var skillName = pageAiSkillToggle.getAttribute('data-page-ai-skill-toggle') || '';
|
|
|
|
|
|
var nextEnabled = pageAiSkillToggle.getAttribute('aria-pressed') !== 'true';
|
|
|
|
|
|
void pageAiToggleSkill(skillName, nextEnabled);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiToolToggle = closestAction(e.target, '[data-page-ai-tool-toggle]');
|
|
|
|
|
|
if (pageAiToolToggle) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var toolName = pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '';
|
|
|
|
|
|
var nextToolEnabled = pageAiToolToggle.getAttribute('aria-pressed') !== 'true';
|
|
|
|
|
|
void pageAiToggleTool(toolName, nextToolEnabled);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSessionResume = closestAction(e.target, '[data-page-ai-session-resume]');
|
|
|
|
|
|
if (pageAiSessionResume) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiResumeBackendSession(pageAiSessionResume.getAttribute('data-page-ai-session-resume') || '').catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSessionRename = closestAction(e.target, '[data-page-ai-session-rename]');
|
|
|
|
|
|
if (pageAiSessionRename) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiRenameBackendSession(pageAiSessionRename.getAttribute('data-page-ai-session-rename') || '').catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSessionDelete = closestAction(e.target, '[data-page-ai-session-delete]');
|
|
|
|
|
|
if (pageAiSessionDelete) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiDeleteBackendSession(pageAiSessionDelete.getAttribute('data-page-ai-session-delete') || '').catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiPermissionAction = closestAction(e.target, '[data-page-ai-permission-action]');
|
|
|
|
|
|
if (pageAiPermissionAction) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageAiResolvePermission(
|
|
|
|
|
|
pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '',
|
|
|
|
|
|
pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny'
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 04:01:52 +08:00
|
|
|
|
var pageAiOpenLocationAction = closestAction(e.target, '[data-page-ai-open-location]');
|
|
|
|
|
|
if (pageAiOpenLocationAction) {
|
2026-05-25 17:36:17 +08:00
|
|
|
|
e.preventDefault();
|
2026-05-26 04:01:52 +08:00
|
|
|
|
var loc = String(pageAiOpenLocationAction.getAttribute('data-page-ai-open-location') || '').trim();
|
|
|
|
|
|
if (loc) pageAiOpenLocation(loc);
|
2026-05-25 17:36:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSession = closestAction(e.target, '[data-page-ai-session]');
|
|
|
|
|
|
if (pageAiSession) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageAiSetActiveSession(pageAiSession.getAttribute('data-page-ai-session') || '');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSuggestion = closestAction(e.target, '[data-page-ai-suggestion]');
|
|
|
|
|
|
if (pageAiSuggestion) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var text = pageAiSuggestion.getAttribute('data-page-ai-suggestion') || '';
|
|
|
|
|
|
var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]');
|
|
|
|
|
|
if (inputNode instanceof HTMLTextAreaElement) {
|
|
|
|
|
|
inputNode.value = text;
|
|
|
|
|
|
inputNode.focus();
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiNewSession = closestAction(e.target, '[data-page-ai-action="new-session"]');
|
|
|
|
|
|
if (pageAiNewSession) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageUiState.pageAiPage = 'chat';
|
|
|
|
|
|
pageAiStartNewSession();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiHistory = closestAction(e.target, '[data-page-ai-action="history"]');
|
|
|
|
|
|
if (pageAiHistory) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
pageAiLoadSessions();
|
|
|
|
|
|
pageUiState.pageAiPage = pageUiState.pageAiPage === 'history' ? 'chat' : 'history';
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
renderPageAiConversation();
|
|
|
|
|
|
if (pageUiState.pageAiPage === 'history') {
|
|
|
|
|
|
void pageAiLoadBackendSessions().catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pageAiSend = closestAction(e.target, '[data-page-ai-action="send"]');
|
|
|
|
|
|
if (pageAiSend) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var input = ensurePageAiDrawer().querySelector('[data-page-ai-input]');
|
|
|
|
|
|
if (input instanceof HTMLTextAreaElement) {
|
|
|
|
|
|
var message = input.value;
|
|
|
|
|
|
input.value = '';
|
|
|
|
|
|
void sendPageAiMessage(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cancelQueuedRun = closestAction(e.target, '[data-page-ai-action="cancel-queued-run"]');
|
|
|
|
|
|
if (cancelQueuedRun) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
void pageAiCancelQueuedRun(cancelQueuedRun.getAttribute('data-page-ai-queue-id'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId || null, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
|
|
|
|
|
|
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') && documentId) {
|
|
|
|
|
|
navigateToDocument(documentId, resolveWorkspaceId(fileRow), { treeView: 'filetree' });
|
|
|
|
|
|
} else if (assetId) {
|
|
|
|
|
|
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow), openTarget: e.shiftKey || e.ctrlKey || e.metaKey ? 'new-window' : 'active-tab' });
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 02:35:08 +08:00
|
|
|
|
if (sidebarPageTree.handlePageTreeClick(e, { closestAction: closestAction })) return;
|
2026-05-25 17:36:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('contextmenu', function(event) {
|
|
|
|
|
|
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
|
|
|
|
|
|
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();
|
|
|
|
|
|
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();
|
|
|
|
|
|
void pasteSidebarFileTreeClipboard(fileTreeRowForKey);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
var delCtx = buildSidebarFileTreeContext('filetree');
|
|
|
|
|
|
if (!evaluateSidebarFileTreeWhen(delCtx, '!workspace.readonly')) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
void deleteSelectedSidebarFileTreeRows(fileTreeRowForKey);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Escape' && isPageSettingsOpen()) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
closePageSettingsPopover();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key && event.key.toLowerCase() === 'p') {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
toggleSearchModal();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
|
|
closeTrashModal();
|
|
|
|
|
|
closeSearchModal();
|
|
|
|
|
|
closeTreeContextMenu();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
|
|
|
|
var aiInput = closestAction(event.target, '[data-page-ai-input]');
|
|
|
|
|
|
if (aiInput instanceof HTMLTextAreaElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
var text = aiInput.value;
|
|
|
|
|
|
aiInput.value = '';
|
|
|
|
|
|
void sendPageAiMessage(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('input', function(event) {
|
|
|
|
|
|
var skillSearch = closestAction(event.target, '[data-page-ai-skill-search]');
|
|
|
|
|
|
if (skillSearch instanceof HTMLInputElement) {
|
|
|
|
|
|
pageUiState.pageAiSkillQuery = skillSearch.value;
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var sessionSearch = closestAction(event.target, '[data-page-ai-session-search]');
|
|
|
|
|
|
if (sessionSearch instanceof HTMLInputElement) {
|
|
|
|
|
|
var sessionQuery = sessionSearch.value;
|
|
|
|
|
|
window.clearTimeout(pageUiState.pageAiSessionSearchTimer || 0);
|
|
|
|
|
|
pageUiState.pageAiSessionSearchTimer = window.setTimeout(function() {
|
|
|
|
|
|
void pageAiSearchBackendSessions(sessionQuery).catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 200);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var memoryEditor = closestAction(event.target, '[data-page-ai-memory-editor]');
|
|
|
|
|
|
if (memoryEditor instanceof HTMLTextAreaElement) {
|
|
|
|
|
|
var section = memoryEditor.getAttribute('data-page-ai-memory-editor') || '';
|
|
|
|
|
|
if (['memory', 'user', 'soul'].indexOf(section) >= 0) {
|
|
|
|
|
|
pageUiState.pageAiProfileMemoryDrafts[section] = memoryEditor.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('change', function(event) {
|
|
|
|
|
|
var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]');
|
|
|
|
|
|
if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) {
|
|
|
|
|
|
var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix';
|
|
|
|
|
|
pageUiState.pageAiAcpRuntime = next;
|
|
|
|
|
|
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
|
|
|
|
|
pageUiState.pageAiSkillError = '';
|
|
|
|
|
|
pageAiPersistSessions();
|
|
|
|
|
|
void pageAiLoadProfiles();
|
|
|
|
|
|
if (next !== 'reasonix') void pageAiLoadProfileMemory();
|
|
|
|
|
|
void pageAiLoadSkills();
|
|
|
|
|
|
void pageAiLoadBackendSessions().catch(function(error) {
|
|
|
|
|
|
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
});
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
renderPageAiProviderButtons();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var pageAiProfileSelect = closestAction(event.target, '[data-page-ai-profile-select]');
|
|
|
|
|
|
if (pageAiProfileSelect instanceof HTMLSelectElement) {
|
|
|
|
|
|
void pageAiSwitchProfile(pageAiProfileSelect.value);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var pageAiContextSelect = closestAction(event.target, '[data-page-ai-context-scope]');
|
|
|
|
|
|
if (pageAiContextSelect instanceof HTMLSelectElement) {
|
|
|
|
|
|
pageAiSetContextScope(pageAiContextSelect.value);
|
|
|
|
|
|
renderPageAiControls();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function initializePageUiSurfaces() {
|
|
|
|
|
|
pageUiState.pageOptions = null;
|
|
|
|
|
|
applyPageOptionsToShell();
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
installMnoteDevHotReload();
|
|
|
|
|
|
|
|
|
|
|
|
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 : {};
|
|
|
|
|
|
selectSidebarFileTreeDocument(detail.documentId, { scrollIntoView: false });
|
|
|
|
|
|
});
|
|
|
|
|
|
restoreSidebarTreeTab();
|
|
|
|
|
|
startLocalFolderSidebarWatch();
|
|
|
|
|
|
})();
|