2026-05-25 01:13:08 +08:00
|
|
|
|
// MNote document conflict panel 运行时外置模块。
|
|
|
|
|
|
// 第一刀:纯 DOM 清理 helper clearSessionConflictSurface。
|
|
|
|
|
|
// 本文件以 type="module" 加载,执行时机可能晚于 inline script。
|
|
|
|
|
|
// web_shell.rs 的 inline clearSessionConflictSurface 会优先委托到
|
|
|
|
|
|
// window.__mnoteDocumentConflictPanelRuntime;
|
|
|
|
|
|
// 模块尚未加载或加载失败时,inline script 保留原地实现兜底。
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除指定 session 所有视图中冲突面板 DOM。
|
|
|
|
|
|
* 只做 DOM 清理,不处理冲突 merge/accept/keep 业务逻辑。
|
|
|
|
|
|
* @param {Object} session - document session 对象
|
|
|
|
|
|
* @param {Object} deps - 依赖注入,从 inline 上下文传入
|
|
|
|
|
|
* @param {Function} deps.sessionViews - session.views 迭代器
|
|
|
|
|
|
*/
|
|
|
|
|
|
function clearSessionConflictSurface(session, deps) {
|
|
|
|
|
|
var views = deps && typeof deps.sessionViews === 'function'
|
|
|
|
|
|
? deps.sessionViews(session)
|
|
|
|
|
|
: [];
|
|
|
|
|
|
views.forEach(function(view) {
|
2026-05-25 08:38:05 +08:00
|
|
|
|
var host = conflictPanelHost(view && view.runtimeDescriptor && view.runtimeDescriptor.root);
|
2026-05-25 01:13:08 +08:00
|
|
|
|
if (!(host instanceof HTMLElement)) return;
|
|
|
|
|
|
host.querySelectorAll('[data-testid="mnote-editor-conflict-panel"]').forEach(function(node) { node.remove(); });
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 08:38:05 +08:00
|
|
|
|
function conflictPanelHost(root) {
|
|
|
|
|
|
if (!(root instanceof Element)) return null;
|
|
|
|
|
|
return root.closest('.document-pane') || root;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function mountSessionConflictPanel(root, panel) {
|
|
|
|
|
|
var host = conflictPanelHost(root);
|
|
|
|
|
|
if (!(host instanceof HTMLElement) || !(panel instanceof HTMLElement)) return false;
|
|
|
|
|
|
var header = host.querySelector('.document-shell-header');
|
|
|
|
|
|
if (header && header.parentNode) {
|
|
|
|
|
|
header.parentNode.insertBefore(panel, header.nextSibling);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
host.prepend(panel);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 01:35:19 +08:00
|
|
|
|
function createSessionConflictPanel(session, message, deps) {
|
|
|
|
|
|
deps = deps || {};
|
|
|
|
|
|
var externalConflictMessage = String(deps.externalConflictMessage || '').trim() || '文件已在外部修改';
|
|
|
|
|
|
var panel = document.createElement('section');
|
|
|
|
|
|
panel.className = 'mnote-editor-conflict-panel';
|
|
|
|
|
|
panel.setAttribute('data-testid', 'mnote-editor-conflict-panel');
|
|
|
|
|
|
panel.setAttribute('role', 'status');
|
|
|
|
|
|
panel.setAttribute('aria-live', 'polite');
|
|
|
|
|
|
|
|
|
|
|
|
var heading = document.createElement('h2');
|
|
|
|
|
|
heading.textContent = '文件冲突';
|
|
|
|
|
|
var text = document.createElement('p');
|
|
|
|
|
|
text.textContent = message || externalConflictMessage;
|
|
|
|
|
|
var meta = document.createElement('div');
|
|
|
|
|
|
meta.className = 'mnote-conflict-meta';
|
|
|
|
|
|
var fileLabel = session && session.sessionKind === 'resource'
|
|
|
|
|
|
? String(session.rootUri || '') + '/' + String(session.resourcePath || session.documentId || '')
|
|
|
|
|
|
: String(session && (session.rootUri || session.documentId) || '');
|
|
|
|
|
|
var sourceLabel = typeof deps.conflictSourceLabel === 'function'
|
|
|
|
|
|
? deps.conflictSourceLabel(session)
|
|
|
|
|
|
: '';
|
|
|
|
|
|
meta.textContent = '文件:' + fileLabel + ' · 来源:' + (sourceLabel || '本地文件变更');
|
|
|
|
|
|
|
|
|
|
|
|
var actions = document.createElement('div');
|
|
|
|
|
|
actions.className = 'mnote-conflict-actions';
|
|
|
|
|
|
var acceptDisk = document.createElement('button');
|
|
|
|
|
|
acceptDisk.type = 'button';
|
|
|
|
|
|
acceptDisk.textContent = '接受磁盘版本';
|
|
|
|
|
|
acceptDisk.setAttribute('data-testid', 'mnote-conflict-accept-disk');
|
|
|
|
|
|
var keepCurrent = document.createElement('button');
|
|
|
|
|
|
keepCurrent.type = 'button';
|
|
|
|
|
|
keepCurrent.textContent = '保留当前编辑器版本';
|
|
|
|
|
|
keepCurrent.setAttribute('data-testid', 'mnote-conflict-keep-current');
|
|
|
|
|
|
var openDiff = document.createElement('button');
|
|
|
|
|
|
openDiff.type = 'button';
|
|
|
|
|
|
openDiff.textContent = '打开 diff';
|
|
|
|
|
|
openDiff.setAttribute('data-testid', 'mnote-conflict-open-diff');
|
|
|
|
|
|
actions.append(acceptDisk, keepCurrent, openDiff);
|
|
|
|
|
|
|
|
|
|
|
|
var diffPanel = document.createElement('div');
|
|
|
|
|
|
diffPanel.className = 'mnote-conflict-diff-panel';
|
|
|
|
|
|
diffPanel.setAttribute('data-testid', 'mnote-conflict-diff-panel');
|
|
|
|
|
|
diffPanel.hidden = true;
|
|
|
|
|
|
panel.append(heading, text, meta, actions, diffPanel);
|
|
|
|
|
|
|
|
|
|
|
|
acceptDisk.addEventListener('click', function() {
|
|
|
|
|
|
if (typeof deps.onAcceptDisk === 'function') deps.onAcceptDisk(session, panel);
|
|
|
|
|
|
});
|
|
|
|
|
|
keepCurrent.addEventListener('click', function() {
|
|
|
|
|
|
if (typeof deps.onKeepCurrent === 'function') deps.onKeepCurrent(session, panel);
|
|
|
|
|
|
});
|
|
|
|
|
|
openDiff.addEventListener('click', function() {
|
|
|
|
|
|
if (typeof deps.onOpenDiff === 'function') deps.onOpenDiff(session, panel);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return panel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 02:44:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 在已有 diffPanel 中填充 current/disk/merge DOM 并绑定回调。
|
|
|
|
|
|
* 不处理 fetch、persist、status 等业务逻辑。
|
|
|
|
|
|
* @param {HTMLElement} panel - 已清空的 diffPanel 元素
|
|
|
|
|
|
* @param {Object} context
|
|
|
|
|
|
* @param {string} context.currentText - 当前编辑器的文本(含占位文案)
|
|
|
|
|
|
* @param {string} context.diskText - 磁盘版本的文本(含占位文案)
|
|
|
|
|
|
* @param {string} [context.currentMergeText] - 写入合并区域的当前编辑器原始文本
|
|
|
|
|
|
* @param {string} [context.diskMergeText] - 写入合并区域的磁盘版本原始文本
|
|
|
|
|
|
* @param {string} [context.mergeDefaultText] - 合并区域默认文本
|
|
|
|
|
|
* @param {Object} deps
|
|
|
|
|
|
* @param {Function} [deps.onUseCurrent] - 可选:返回当前版本写入合并区域的文本
|
|
|
|
|
|
* @param {Function} [deps.onUseDisk] - 可选:返回磁盘版本写入合并区域的文本
|
|
|
|
|
|
* @param {Function} deps.onSaveMerge - 保存合并结果回调,接收合并文本字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
function populateSessionConflictDiffPanel(panel, context, deps) {
|
|
|
|
|
|
context = context || {};
|
|
|
|
|
|
deps = deps || {};
|
|
|
|
|
|
var currentText = context.currentText || '';
|
|
|
|
|
|
var diskText = context.diskText || '';
|
|
|
|
|
|
var currentMergeText = Object.prototype.hasOwnProperty.call(context, 'currentMergeText')
|
|
|
|
|
|
? String(context.currentMergeText || '')
|
|
|
|
|
|
: currentText;
|
|
|
|
|
|
var diskMergeText = Object.prototype.hasOwnProperty.call(context, 'diskMergeText')
|
|
|
|
|
|
? String(context.diskMergeText || '')
|
|
|
|
|
|
: diskText;
|
|
|
|
|
|
var mergeDefaultText = context.mergeDefaultText || currentText || diskText || '';
|
|
|
|
|
|
|
|
|
|
|
|
var current = document.createElement('pre');
|
|
|
|
|
|
current.setAttribute('data-testid', 'mnote-conflict-current-text');
|
|
|
|
|
|
current.textContent = currentText;
|
|
|
|
|
|
var disk = document.createElement('pre');
|
|
|
|
|
|
disk.setAttribute('data-testid', 'mnote-conflict-disk-text');
|
|
|
|
|
|
disk.textContent = diskText;
|
|
|
|
|
|
var currentTitle = document.createElement('h3');
|
|
|
|
|
|
currentTitle.textContent = '当前编辑器版本';
|
|
|
|
|
|
var diskTitle = document.createElement('h3');
|
|
|
|
|
|
diskTitle.textContent = '磁盘版本';
|
|
|
|
|
|
var currentBox = document.createElement('section');
|
|
|
|
|
|
currentBox.append(currentTitle, current);
|
|
|
|
|
|
var diskBox = document.createElement('section');
|
|
|
|
|
|
diskBox.append(diskTitle, disk);
|
|
|
|
|
|
var mergeTitle = document.createElement('h3');
|
|
|
|
|
|
mergeTitle.textContent = '合并结果';
|
|
|
|
|
|
var mergeText = document.createElement('textarea');
|
|
|
|
|
|
mergeText.setAttribute('data-testid', 'mnote-conflict-merge-text');
|
|
|
|
|
|
mergeText.value = mergeDefaultText;
|
|
|
|
|
|
var mergeActions = document.createElement('div');
|
|
|
|
|
|
mergeActions.className = 'mnote-conflict-actions';
|
|
|
|
|
|
var useCurrent = document.createElement('button');
|
|
|
|
|
|
useCurrent.type = 'button';
|
|
|
|
|
|
useCurrent.textContent = '使用当前版本';
|
|
|
|
|
|
useCurrent.setAttribute('data-testid', 'mnote-conflict-merge-use-current');
|
|
|
|
|
|
var useDisk = document.createElement('button');
|
|
|
|
|
|
useDisk.type = 'button';
|
|
|
|
|
|
useDisk.textContent = '使用磁盘版本';
|
|
|
|
|
|
useDisk.setAttribute('data-testid', 'mnote-conflict-merge-use-disk');
|
|
|
|
|
|
var saveMerge = document.createElement('button');
|
|
|
|
|
|
saveMerge.type = 'button';
|
|
|
|
|
|
saveMerge.textContent = '写回合并结果';
|
|
|
|
|
|
saveMerge.setAttribute('data-testid', 'mnote-conflict-merge-save');
|
|
|
|
|
|
mergeActions.append(useCurrent, useDisk, saveMerge);
|
|
|
|
|
|
var mergeBox = document.createElement('section');
|
|
|
|
|
|
mergeBox.className = 'mnote-conflict-merge-box';
|
|
|
|
|
|
mergeBox.append(mergeTitle, mergeText, mergeActions);
|
|
|
|
|
|
panel.append(currentBox, diskBox, mergeBox);
|
|
|
|
|
|
|
|
|
|
|
|
useCurrent.addEventListener('click', function() {
|
|
|
|
|
|
var nextText = typeof deps.onUseCurrent === 'function'
|
|
|
|
|
|
? deps.onUseCurrent(context)
|
|
|
|
|
|
: currentMergeText;
|
|
|
|
|
|
mergeText.value = typeof nextText === 'string' ? nextText : currentMergeText;
|
|
|
|
|
|
});
|
|
|
|
|
|
useDisk.addEventListener('click', function() {
|
|
|
|
|
|
var nextText = typeof deps.onUseDisk === 'function'
|
|
|
|
|
|
? deps.onUseDisk(context)
|
|
|
|
|
|
: diskMergeText;
|
|
|
|
|
|
mergeText.value = typeof nextText === 'string' ? nextText : diskMergeText;
|
|
|
|
|
|
});
|
|
|
|
|
|
saveMerge.addEventListener('click', function() {
|
|
|
|
|
|
if (typeof deps.onSaveMerge === 'function') {
|
|
|
|
|
|
deps.onSaveMerge(mergeText.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 03:05:57 +08:00
|
|
|
|
function runSessionConflictAction(action, deps) {
|
|
|
|
|
|
deps = deps || {};
|
|
|
|
|
|
if (typeof action !== 'function') return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
var result = action();
|
|
|
|
|
|
if (result && typeof result.then === 'function') {
|
|
|
|
|
|
result.catch(function(error) {
|
|
|
|
|
|
if (typeof deps.onError === 'function') deps.onError(error);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (typeof deps.onError === 'function') deps.onError(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 01:13:08 +08:00
|
|
|
|
window.__mnoteDocumentConflictPanelRuntime = {
|
2026-05-25 01:35:19 +08:00
|
|
|
|
clearSessionConflictSurface: clearSessionConflictSurface,
|
2026-05-25 02:44:19 +08:00
|
|
|
|
createSessionConflictPanel: createSessionConflictPanel,
|
2026-05-25 08:38:05 +08:00
|
|
|
|
mountSessionConflictPanel: mountSessionConflictPanel,
|
2026-05-25 03:05:57 +08:00
|
|
|
|
populateSessionConflictDiffPanel: populateSessionConflictDiffPanel,
|
|
|
|
|
|
runSessionConflictAction: runSessionConflictAction
|
2026-05-25 01:13:08 +08:00
|
|
|
|
};
|