修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)

一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。

二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。

三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
This commit is contained in:
admin committed 2026-10-08 22:29:52 +08:00
1 parent e03465c398
commit 237a09a5b0
161 files changed
+19429 -2

No files matched your search

@@ -0,0 +1,96 @@
type Field = { id: string; label: string; credential: string; revision: string; configured: boolean; storage: string; ui?: { title?: string; placeholder?: string; saveLabel?: string } };
type Metadata = { fields: Field[]; page: { title?: string; label?: string; saveLabel?: string }; outcome: string };
type SaveResult = { status: string; results?: { credential: string; status: string }[] };
const get = <T extends HTMLElement = HTMLElement>(id: string) => document.getElementById(id) as T;
const form = get<HTMLFormElement>('credential-form');
const button = get<HTMLButtonElement>('save');
let inputs: HTMLInputElement[] = [];
let meta: Metadata;
let busy = false;
const saveLabel = () => inputs.some((input, index) => input.value.trim() && meta.fields[index].configured)
? '替换并保存' : (meta.page.saveLabel ?? (meta.fields.length === 1 ? meta.fields[0].ui?.saveLabel : undefined) ?? '保存');
async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
let response: Response;
try {
response = await fetch(url, { ...options, credentials: 'same-origin', cache: 'no-store',
headers: { 'Content-Type': 'application/json', 'X-Local-Request': '1', ...options.headers }, signal: AbortSignal.timeout(25_000) });
} catch { throw new Error('未收到服务回复,请刷新确认保存结果,不要重复提交。'); }
const result = await response.json();
if (!response.ok) throw new Error(result.error ?? '操作未完成,请重试。');
return result as T;
}
function message(text: string) { const node = get('message'); node.textContent = text; node.hidden = false; }
function clearInputs() { inputs.forEach(input => { input.value = ''; }); }
function done() {
clearInputs(); form.hidden = true; get('heading').textContent = '已保存';
get('hint').hidden = true; message('可以关闭此页,回到对话继续。');
}
function update() {
button.disabled = busy || !meta || !['waiting', 'partial'].includes(meta.outcome)
|| !inputs.some(input => input.value.trim())
|| inputs.some((input, index) => !meta.fields[index].configured && !input.value.trim());
if (meta && !busy) button.textContent = saveLabel();
}
function render() {
clearInputs(); inputs = [];
const multi = meta.fields.length > 1;
form.classList.toggle('multi', multi);
const container = get('fields'); container.replaceChildren();
meta.fields.forEach((field, index) => {
const wrapper = document.createElement('div'); wrapper.className = 'field';
const label = document.createElement('label'); label.htmlFor = 'secret-' + index; label.textContent = field.label;
if (!multi) label.className = 'visually-hidden';
if (field.configured) {
const state = document.createElement('span'); state.className = 'field-state'; state.textContent = '已配置'; label.append(state);
}
const input = document.createElement('input'); input.id = label.htmlFor; input.type = 'password';
input.autocomplete = 'new-password'; input.autocapitalize = 'off'; input.spellcheck = false; input.maxLength = 2500;
input.required = !field.configured; input.setAttribute('aria-describedby', 'hint message');
input.placeholder = field.configured ? '留空保留,输入则替换' : (field.ui?.placeholder ?? '粘贴 API Key');
input.disabled = !['waiting', 'partial'].includes(meta.outcome);
wrapper.append(label, input); container.append(wrapper); inputs.push(input);
});
get('context').textContent = meta.page.label ?? (multi ? meta.fields.length + ' 项凭据' : meta.fields[0].label);
const title = meta.page.title ?? (multi ? '输入密钥' : meta.fields[0].ui?.title) ?? '输入密钥';
get('heading').textContent = title; document.title = title;
const storage = [...new Set(meta.fields.map(field => field.storage))].join('、');
get('hint').textContent = '仅保存到' + storage + (meta.fields.some(field => field.configured) ? ' · 已配置项留空保留' : '');
update();
if (meta.outcome === 'saved') done();
else if (meta.outcome === 'partial') message('上次仅部分保存。请核对已配置项,补填未完成项后重试。');
else if (meta.outcome !== 'waiting') message('本次配置已结束,请重新打开入口。');
}
form.addEventListener('input', () => { update(); get('message').hidden = true; });
form.addEventListener('submit', async event => {
event.preventDefault(); if (busy || button.disabled) return;
busy = true; update(); inputs.forEach(input => { input.disabled = true; }); button.textContent = '正在保存…';
try {
const entries = inputs.flatMap((input, index) => input.value.trim() ? [{ credential: meta.fields[index].credential,
value: input.value, revision: meta.fields[index].revision, replaceExisting: meta.fields[index].configured }] : []);
const result = await request<SaveResult>('/api/save', { method: 'POST', body: JSON.stringify({ entries }) });
if (result.status === 'saved') { meta.outcome = 'saved'; done(); }
else {
clearInputs(); meta = await request<Metadata>('/api/meta'); render();
const statuses: Record<string, string> = { saved: '已保存', failed: '未确认成功', not_attempted: '未尝试' };
message((result.results ?? []).map(item => (meta.fields.find(field => field.credential === item.credential)?.label ?? '凭据') + ':' + (statuses[item.status] ?? '请核对')).join(';')
+ '。请核对状态后重新填写未完成项。');
}
} catch (error) {
clearInputs();
try { meta = await request<Metadata>('/api/meta'); render(); }
catch { meta.outcome = 'unknown'; }
if (meta.outcome !== 'saved') message(error instanceof Error ? error.message : '保存未完成,请刷新确认结果。');
} finally {
clearInputs(); busy = false; inputs.forEach(input => { input.disabled = !['waiting', 'partial'].includes(meta.outcome); }); update();
}
});
window.addEventListener('pagehide', clearInputs);
async function initialize() {
const token = location.hash.slice(1);
if (token) {
history.replaceState(null, '', location.pathname);
await request('/api/session', { method: 'POST', headers: { Authorization: 'Bearer ' + token }, body: '{}' });
}
meta = await request<Metadata>('/api/meta'); render();
}
initialize().catch(error => { get('context').textContent = '暂时无法连接'; message(error instanceof Error ? error.message : '请重新打开配置入口。'); });