// MNote 文件树上下文菜单运行时外置模块。 // 当前先承接 CommandContext 构造与 when 表达式求值,树语义仍由 Rust/kernel 主导。 function commandContextResourceKindForRow(row) { if (!(row instanceof HTMLElement)) return 'unknown'; var rowKind = String(row.getAttribute('data-row-kind') || '').trim(); var assetType = String(row.getAttribute('data-asset-type') || '').trim(); var assetId = String(row.getAttribute('data-asset-id') || '').trim(); if (rowKind === 'folder' || rowKind === 'directory' || rowKind === 'asset-folder') return 'folder'; if (rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') return 'page'; if (assetType) return assetType; if (assetId) return 'asset'; return rowKind || 'unknown'; } function commandContextDirtyState(value) { var state = String(value || '').trim(); if (!state) return 'clean'; var lowered = state.toLowerCase(); if (lowered === 'dirty' || lowered === 'stale' || lowered === 'deleted') return lowered; if (lowered === 'externalmodified' || lowered === 'external_modified' || lowered === 'external-change-conflict') return 'external_modified'; if (lowered === 'hasexternalconflict' || lowered === 'has_external_conflict') return 'has_external_conflict'; return lowered === 'clean' || lowered === 'saved' ? 'clean' : lowered; } function commandContextIsDirtyState(value) { return commandContextDirtyState(value) !== 'clean'; } function commandContextEditorMatchesRow(editor, row) { if (!editor || !(row instanceof HTMLElement)) return false; var rowDocumentId = String(row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '').trim(); if (rowDocumentId && String(editor.documentId || '').trim() === rowDocumentId) return true; var rowAssetId = String(row.getAttribute('data-asset-id') || '').trim(); if (rowAssetId && String(editor.assetId || editor.workspacePath && editor.workspacePath.assetId || '').trim() === rowAssetId) return true; var rowRelativePath = String(row.getAttribute('data-local-relative-path') || '').trim(); var editorRelativePath = String(editor.workspacePath && (editor.workspacePath.relativePath || editor.workspacePath.localRelativePath) || '').trim(); return Boolean(rowRelativePath && editorRelativePath && rowRelativePath === editorRelativePath); } function commandContextDirtyStateForRows(rows, openEditorsSnapshot, bufferState) { if (bufferState && typeof bufferState === 'object') { var directState = bufferState.dirtyState || bufferState.state || bufferState.status || ''; if (commandContextIsDirtyState(directState)) return commandContextDirtyState(directState); } var editors = []; if (openEditorsSnapshot && Array.isArray(openEditorsSnapshot.editors)) editors = editors.concat(openEditorsSnapshot.editors); if (openEditorsSnapshot && Array.isArray(openEditorsSnapshot.resourceEditors)) editors = editors.concat(openEditorsSnapshot.resourceEditors); for (var r = 0; r < rows.length; r += 1) { var row = rows[r]; for (var i = 0; i < editors.length; i += 1) { var editor = editors[i]; if (!commandContextEditorMatchesRow(editor, row)) continue; var editorState = editor.dirtyState || editor.dirtyGuard || ''; if (commandContextIsDirtyState(editorState)) return commandContextDirtyState(editorState); } } return 'clean'; } function selectedRowsFromDeps(deps, fallbackRow) { var selection = deps && deps.selection ? deps.selection : {}; var rowIds = Array.isArray(selection.selectedRowIds) ? selection.selectedRowIds : Array.from(selection.selectedRowIds || []); var rows = []; var queryRowById = deps && typeof deps.queryRowById === 'function' ? deps.queryRowById : function() { return null; }; for (var i = 0; i < rowIds.length; i += 1) { var row = queryRowById(rowIds[i]); if (row instanceof HTMLElement) rows.push(row); } if (!rows.length && fallbackRow instanceof HTMLElement) rows.push(fallbackRow); return rows; } function buildFileTreeCommandContext(row, openEditorsSnapshot, bufferState, deps) { deps = deps || {}; var rows = selectedRowsFromDeps(deps, row); var resourceKinds = {}; for (var j = 0; j < rows.length; j += 1) { resourceKinds[commandContextResourceKindForRow(rows[j])] = true; } var rkKeys = Object.keys(resourceKinds); var currentSourceKind = typeof deps.currentSourceKind === 'function' ? deps.currentSourceKind : function() { return ''; }; var readonly = typeof deps.workspaceReadonly === 'function' ? deps.workspaceReadonly() : false; var dirtyState = commandContextDirtyStateForRows(rows, openEditorsSnapshot, bufferState); var editorDirty = commandContextIsDirtyState(dirtyState); var targetKind = commandContextResourceKindForRow(row); return { 'workspace.sourceKind': currentSourceKind() || '', 'workspace.readonly': readonly === true, 'tree.focusKind': deps.focusKind || 'file_tree', 'tree.selectionCount': rows.length, 'tree.selectionResourceKind': rkKeys.length === 1 ? rkKeys[0] : rows.length ? 'mixed' : targetKind, 'tree.targetResourceKind': targetKind, 'tree.targetRowKind': row instanceof HTMLElement ? String(row.getAttribute('data-row-kind') || '').trim() : '', 'tree.targetIsFolder': targetKind === 'folder', 'tree.targetIsAsset': Boolean(row instanceof HTMLElement && String(row.getAttribute('data-asset-id') || '').trim()), 'editor.dirty': editorDirty, 'editor.dirtyState': dirtyState, 'editor.hasSelection': Boolean(deps.editorHasSelection), 'ai.canWrite': readonly !== true && !editorDirty, }; } function buildSidebarFileTreeContext(kind, deps) { var targetRow = deps && deps.targetRow instanceof HTMLElement ? deps.targetRow : null; return buildFileTreeCommandContext(targetRow, deps && deps.openEditorsSnapshot, deps && deps.bufferState, Object.assign({}, deps || {}, { focusKind: kind === 'filetree' ? 'file_tree' : kind === 'page' ? 'page_tree' : kind, })); } function buildLegacySidebarFileTreeContext(kind, deps) { var selection = deps && deps.selection ? deps.selection : {}; var rowIds = Array.isArray(selection.selectedRowIds) ? selection.selectedRowIds : Array.from(selection.selectedRowIds || []); var rows = []; var queryRowById = deps && typeof deps.queryRowById === 'function' ? deps.queryRowById : function() { return null; }; for (var i = 0; i < rowIds.length; i += 1) { var row = queryRowById(rowIds[i]); if (row instanceof HTMLElement) rows.push(row); } var resourceKinds = {}; for (var j = 0; j < rows.length; j += 1) { var rk = rows[j].getAttribute('data-row-kind') || 'unknown'; resourceKinds[rk] = true; } var rkKeys = Object.keys(resourceKinds); var currentSourceKind = deps && typeof deps.currentSourceKind === 'function' ? deps.currentSourceKind : function() { return ''; }; var readonly = deps && typeof deps.workspaceReadonly === 'function' ? deps.workspaceReadonly() : false; return { 'workspace.sourceKind': currentSourceKind() || '', 'workspace.readonly': readonly === true, 'tree.focusKind': kind === 'filetree' ? 'file_tree' : kind === 'page' ? 'page_tree' : kind, 'tree.selectionCount': rows.length, 'tree.selectionResourceKind': rkKeys.length === 1 ? rkKeys[0] : 'mixed' }; } function evaluateSidebarFileTreeWhen(ctx, expr) { if (!expr) return true; try { expr = String(expr || '').trim(); var tokens = tokenizeWhenExpression(expr); var pos = 0; function peek() { return tokens[pos]; } function consume() { return tokens[pos++]; } function expect(t) { var tok = consume(); if (!tok || tok.t !== t) throw new Error('Expected ' + t + ' got ' + (tok ? tok.t : 'EOF')); return tok; } function ctxVal(key) { var v = ctx[key]; if (v === undefined || v === null) return ''; return v; } function compareValue(cv, value, equal) { if (value === 'true') { var truth = cv === true || cv === 'true'; return equal ? truth : !truth; } if (value === 'false') { var falsy = cv === false || cv === '' || cv === 'false'; return equal ? falsy : !falsy; } var num = Number(value); if (!isNaN(num) && String(num) === value) { return equal ? Number(cv) === num : Number(cv) !== num; } return equal ? String(cv) === value : String(cv) !== value; } function parseOr() { var left = parseAnd(); while (peek() && peek().t === '||') { consume(); var right = parseAnd(); left = left || right; } return left; } function parseAnd() { var left = parsePrimary(); while (peek() && peek().t === '&&') { consume(); var right = parsePrimary(); left = left && right; } return left; } function parsePrimary() { if (!peek()) throw new Error('Unexpected EOF'); if (peek().t === '(') { consume(); var grouped = parseOr(); expect(')'); return grouped; } if (peek().t === '!') { consume(); var notTok = consume(); if (!notTok || notTok.t !== 'id') throw new Error('Expected key after !'); return !ctxVal(notTok.v); } if (peek().t === 'id') { var keyTok = consume(); if (peek() && peek().t === '==') { consume(); var eqTok = consume(); if (!eqTok) throw new Error('Expected value after =='); return compareValue(ctxVal(keyTok.v), eqTok.t === 'str' ? eqTok.v : (eqTok.v || ''), true); } if (peek() && peek().t === '!=') { consume(); var neTok = consume(); if (!neTok) throw new Error('Expected value after !='); return compareValue(ctxVal(keyTok.v), neTok.t === 'str' ? neTok.v : (neTok.v || ''), false); } var cv = ctxVal(keyTok.v); if (typeof cv === 'boolean') return cv; if (typeof cv === 'number') return cv !== 0; return cv !== '' && cv !== undefined && cv !== null; } throw new Error('Unexpected token: ' + peek().t); } return parseOr(); } catch (_) { return true; } } function tokenizeWhenExpression(expr) { var tokens = []; var i = 0; while (i < expr.length) { var ch = expr[i]; if (ch === ' ' || ch === '\t') { i += 1; continue; } if (ch === '(') { tokens.push({ t: '(' }); i += 1; continue; } if (ch === ')') { tokens.push({ t: ')' }); i += 1; continue; } if (ch === '&' && expr[i + 1] === '&') { tokens.push({ t: '&&' }); i += 2; continue; } if (ch === '|' && expr[i + 1] === '|') { tokens.push({ t: '||' }); i += 2; continue; } if (ch === '=' && expr[i + 1] === '=') { tokens.push({ t: '==' }); i += 2; continue; } if (ch === '!' && expr[i + 1] === '=') { tokens.push({ t: '!=' }); i += 2; continue; } if (ch === '!') { tokens.push({ t: '!' }); i += 1; continue; } if (ch === '"' || ch === "'") { i += 1; var str = ''; while (i < expr.length && expr[i] !== ch) { str += expr[i]; i += 1; } if (i < expr.length) i += 1; tokens.push({ t: 'str', v: str }); continue; } if ( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '_' || ch === '.' ) { var id = ''; while ( i < expr.length && ( (expr[i] >= 'a' && expr[i] <= 'z') || (expr[i] >= 'A' && expr[i] <= 'Z') || (expr[i] >= '0' && expr[i] <= '9') || expr[i] === '_' || expr[i] === '.' ) ) { id += expr[i]; i += 1; } tokens.push({ t: 'id', v: id }); continue; } throw new Error('Unexpected char: ' + ch); } return tokens; } window.__mnoteFileTreeContextMenuRuntime = { buildFileTreeCommandContext: buildFileTreeCommandContext, buildSidebarFileTreeContext: buildSidebarFileTreeContext, buildLegacySidebarFileTreeContext: buildLegacySidebarFileTreeContext, evaluateSidebarFileTreeWhen: evaluateSidebarFileTreeWhen, tokenizeWhenExpression: tokenizeWhenExpression };