git-sync-web 自动提交 2026-09-09 12:38:18

This commit is contained in:
tamakyi
2026-09-09 12:38:18 +08:00
parent 0602b89276
commit d4f10ab574
5 changed files with 494 additions and 0 deletions
+114
View File
@@ -108,6 +108,7 @@ function connect() {
if (i >= 0) repos[i] = msg.data; else repos.push(msg.data);
render();
updateConflictActions();
if (current === msg.repo && !$('#tab-diff').hidden) loadDiff(msg.repo);
} else if (msg.type === 'log') {
appendLiveLine(msg);
if (current === msg.repo && !$('#tab-log').hidden) appendLogLine(`[${msg.time}] ${msg.msg}`);
@@ -206,6 +207,8 @@ function showTab(t) {
document.querySelectorAll('.tab-pane').forEach(p => { p.hidden = p.id !== 'tab-' + t; });
if (t === 'log' && current) loadLog(current);
if (t === 'history' && current) loadHistory(current);
if (t === 'diff' && current) loadDiff(current);
if (t === 'file' && current) loadTree(current);
if (t === 'conflict' && current) { loadConflicts(current); updateConflictActions(); }
if (t === 'stats' && current) loadStats(current);
}
@@ -367,6 +370,117 @@ function updateConflictActions() {
}
}
// ---------- 差异显示(网页端) ----------
function loadDiff(name) {
const scope = $('#f-diff-scope').value;
const box = $('#diffbox');
const stat = $('#diff-stat');
box.textContent = '加载中…';
stat.innerHTML = '';
api(`/api/repos/${name}/diff?scope=${encodeURIComponent(scope)}`).then(d => {
renderDiff(box, d.text || '(无差异)');
if (d.files && d.files.length) {
stat.innerHTML = d.files.map(f =>
`<span class="df ${f.add ? 'df-add' : ''} ${f.del ? 'df-del' : ''}">${esc(f.path)} <b style="color:var(--green)">+${f.add}</b> <b style="color:var(--red)">-${f.del}</b></span>`
).join('');
} else if (d.stat) {
stat.textContent = d.stat.trim();
}
}).catch(err => { box.textContent = '差异加载失败:' + err.message; });
}
function renderDiff(box, text) {
box.innerHTML = '';
const lines = String(text).split('\n');
const frag = document.createDocumentFragment();
lines.forEach(line => {
const pre = document.createElement('div');
pre.className = 'diff-line';
if (line.startsWith('+')) pre.classList.add('diff-add');
else if (line.startsWith('-')) pre.classList.add('diff-del');
else if (line.startsWith('@@')) pre.classList.add('diff-hunk');
pre.textContent = line === '' ? ' ' : line;
frag.appendChild(pre);
});
box.appendChild(frag);
}
// ---------- 文件编辑(网页端) ----------
let fileCurrent = null;
function loadTree(name) {
const list = $('#file-list');
list.textContent = '加载中…';
api(`/api/repos/${name}/tree`).then(d => {
const files = d.files || [];
if (!files.length) { list.textContent = '(无文件)'; return; }
list.innerHTML = files.map(f =>
`<div class="file-item" data-path="${esc(f.path)}" title="${esc(f.path)}">${esc(f.path)}</div>`
).join('');
}).catch(err => { list.textContent = '文件列表加载失败:' + err.message; });
}
async function loadFile(name, path) {
const editor = $('#file-editor');
editor.value = '加载中…';
editor.disabled = false;
$('#file-current').textContent = path;
fileCurrent = path;
try {
const d = await api(`/api/repos/${name}/file?path=${encodeURIComponent(path)}`);
if (d.binary) {
editor.value = `(二进制文件,大小 ${d.size} 字节,无法在网页编辑)`;
editor.disabled = true;
} else {
editor.value = d.content || '';
editor.disabled = false;
}
} catch (err) {
editor.value = '读取失败:' + err.message;
editor.disabled = true;
}
}
async function saveFile(commit) {
const name = current;
const path = fileCurrent;
if (!name || !path) { alert('请先在左侧选择一个文件'); return; }
const editor = $('#file-editor');
if (editor.disabled) { alert('当前文件不可编辑(二进制或读取失败)'); return; }
const content = editor.value;
try {
// 先写盘
await api(`/api/repos/${name}/file`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, content })
});
if (commit) {
await api(`/api/repos/${name}/commit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, message: $('#file-msg').value })
});
alert('已保存并提交');
} else {
// 保存后自动切到差异预览
$('#f-diff-scope').value = 'working';
showTab('diff');
alert('已保存到磁盘(未提交)。可在「差异」tab 查看改动,或稍后由自动同步提交。');
}
} catch (err) { alert('保存失败:' + err.message); }
}
$('#f-diff-scope').addEventListener('change', () => { if (current && !$('#tab-diff').hidden) loadDiff(current); });
$('#btn-diff-refresh').addEventListener('click', () => { if (current) loadDiff(current); });
$('#btn-file-refresh').addEventListener('click', () => { if (current) loadTree(current); });
$('#file-list').addEventListener('click', (e) => {
const item = e.target.closest('.file-item');
if (!item || !current) return;
loadFile(current, item.dataset.path);
});
$('#btn-file-save').addEventListener('click', () => saveFile(false));
$('#btn-file-commit').addEventListener('click', () => saveFile(true));
// ---------- 统计 ----------
function dateStr(d) {
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
+34
View File
@@ -45,6 +45,8 @@
<div class="tabs" id="m-tabs">
<button data-tab="log" class="tab active">日志</button>
<button data-tab="history" class="tab">历史</button>
<button data-tab="diff" class="tab">差异</button>
<button data-tab="file" class="tab">文件</button>
<button data-tab="conflict" class="tab">冲突</button>
<button data-tab="stats" class="tab">统计</button>
<button data-tab="config" class="tab">配置</button>
@@ -56,6 +58,38 @@
<div id="tab-history" class="tab-pane" hidden>
<div id="commit-list" class="commit-list">加载中…</div>
</div>
<div id="tab-diff" class="tab-pane" hidden>
<div class="diff-head">
<label>范围
<select id="f-diff-scope">
<option value="working">工作区(未暂存)</option>
<option value="staged">暂存区</option>
<option value="all">全部(含已提交未推送)</option>
<option value="remote">与远程对比</option>
</select>
</label>
<button class="btn btn-sm" id="btn-diff-refresh">刷新</button>
</div>
<div id="diff-stat" class="diff-stat"></div>
<pre id="diffbox" class="diffbox">加载中…</pre>
</div>
<div id="tab-file" class="tab-pane" hidden>
<div class="file-head">
<button class="btn btn-sm" id="btn-file-refresh">刷新文件列表</button>
<span id="file-current" class="file-current"></span>
</div>
<div class="file-body">
<div id="file-list" class="file-list">加载中…</div>
<div class="file-edit-col">
<textarea id="file-editor" class="file-editor" placeholder="选择左侧文件进行编辑" spellcheck="false"></textarea>
<div class="file-actions">
<input id="file-msg" class="file-msg" placeholder="提交说明(可选)">
<button class="btn btn-sm" id="btn-file-save">保存</button>
<button class="btn btn-sm btn-primary" id="btn-file-commit">保存并提交</button>
</div>
</div>
</div>
</div>
<div id="tab-conflict" class="tab-pane" hidden>
<div class="manual-actions">
<button class="btn btn-sm" id="btn-fetch">获取</button>
+32
View File
@@ -121,3 +121,35 @@ textarea { font-family: Consolas, monospace; resize: vertical; }
.form-actions { display: flex; gap: 8px; margin-top: 16px; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }
/* ---------- 差异显示(网页端) ---------- */
.diff-head { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
.diff-head label { font-size: 12px; color: var(--muted); display: flex; gap: 6px; align-items: center; }
.diff-stat { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; font-size: 12px; }
.diff-stat .df { background: var(--gray-bg); border-radius: 6px; padding: 1px 8px; font-family: Consolas, monospace; }
.diff-stat .df.df-add { background: var(--green-bg); }
.diff-stat .df.df-del { background: var(--red-bg); }
.diffbox { margin: 0; background: #f6f7f9; border: 1px solid var(--border); padding: 10px; border-radius: 8px; font: 12px/1.5 Consolas, monospace; white-space: pre; word-break: break-all; max-height: 56vh; overflow: auto; }
.diff-line { white-space: pre; }
.diff-line.diff-add { background: var(--green-bg); color: var(--green); }
.diff-line.diff-del { background: var(--red-bg); color: var(--red); }
.diff-line.diff-hunk { color: var(--blue); background: #eef3f8; }
/* ---------- 文件编辑(网页端) ---------- */
.file-head { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
.file-current { font-size: 12px; color: var(--muted); font-family: Consolas, monospace; }
.file-body { display: flex; gap: 14px; align-items: stretch; min-height: 420px; }
.file-list { flex: none; width: 280px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 6px; background: #fff; max-height: 56vh; }
.file-list:empty::after { content: '(无文件)'; color: var(--muted); font-size: 12px; padding: 4px; }
.file-item { padding: 4px 8px; border-radius: 6px; font-size: 12.5px; font-family: Consolas, monospace; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-item:hover { background: #f0f3f6; }
.file-edit-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.file-editor { flex: 1; min-height: 360px; width: 100%; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font: 12.5px/1.55 Consolas, monospace; color: var(--text); background: #fff; white-space: pre; overflow: auto; }
.file-editor:disabled { background: #f6f7f9; color: var(--muted); }
.file-actions { display: flex; gap: 8px; align-items: center; }
.file-msg { flex: 1; }
@media (max-width: 760px) {
.file-body { flex-direction: column; }
.file-list { width: 100%; max-height: 200px; }
}