修: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:
1 parent
e03465c398
commit
237a09a5b0
161 files changed
+19429
-2
No files matched your search
@@ -0,0 +1,145 @@
|
||||
"use strict";
|
||||
const get = (id) => document.getElementById(id);
|
||||
const form = get('credential-form');
|
||||
const button = get('save');
|
||||
let inputs = [];
|
||||
let meta;
|
||||
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(url, options = {}) {
|
||||
let 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;
|
||||
}
|
||||
function message(text) { 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('/api/save', { method: 'POST', body: JSON.stringify({ entries }) });
|
||||
if (result.status === 'saved') {
|
||||
meta.outcome = 'saved';
|
||||
done();
|
||||
}
|
||||
else {
|
||||
clearInputs();
|
||||
meta = await request('/api/meta');
|
||||
render();
|
||||
const statuses = { 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('/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('/api/meta');
|
||||
render();
|
||||
}
|
||||
initialize().catch(error => { get('context').textContent = '暂时无法连接'; message(error instanceof Error ? error.message : '请重新打开配置入口。'); });
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" rx="12" fill="#292929"/><path d="M12 19v-4a8 8 0 0 1 16 0v4" fill="none" stroke="#eeeeee" stroke-width="3"/><rect x="9" y="18" width="22" height="16" rx="5" fill="#eeeeee"/><circle cx="20" cy="25" r="2" fill="#292929"/></svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<title>输入密钥</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<script type="module" src="/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1 id="heading">输入密钥</h1>
|
||||
<p id="context" class="context">正在连接…</p>
|
||||
<form id="credential-form" autocomplete="off">
|
||||
<div id="fields">
|
||||
<label for="secret" class="visually-hidden">密钥</label>
|
||||
<input id="secret" name="secret" type="password" placeholder="粘贴 API Key" autocomplete="new-password" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="2500" required disabled aria-describedby="hint message" />
|
||||
</div>
|
||||
<button id="save" type="submit" disabled>保存</button>
|
||||
</form>
|
||||
<p id="hint" class="hint">仅保存到本机系统凭据库</p>
|
||||
<p id="message" class="message" role="status" aria-live="polite" hidden></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,143 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
:root {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: #282828;
|
||||
background: #fafafa;
|
||||
font-synthesis: none;
|
||||
color-scheme: light;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100svh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
}
|
||||
main {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
margin-top: -5vh;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 20px;
|
||||
font-weight: 580;
|
||||
letter-spacing: -.45px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.context {
|
||||
margin: 0 0 19px;
|
||||
font-size: 11.5px;
|
||||
color: #777777;
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
}
|
||||
#fields, .field { display: contents; }
|
||||
form.multi { grid-template-columns: 1fr; gap: 18px; }
|
||||
.multi #fields { display: grid; gap: 17px; }
|
||||
.multi .field { display: grid; gap: 7px; }
|
||||
.multi label { font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
|
||||
.field-state { color: #818181; font-size: 10.5px; margin-left: 7px; }
|
||||
.multi button { justify-self: end; height: 36px; }
|
||||
body:has(form.multi) { align-items: safe center; }
|
||||
body:has(form.multi) main { margin-top: 0; }
|
||||
input, button {
|
||||
font: inherit;
|
||||
border-radius: 8px;
|
||||
height: 42px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: #fff;
|
||||
border: 1px solid #dddddd;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
color: #303030;
|
||||
outline: none;
|
||||
box-shadow: 0 1px 2px #00000003;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
}
|
||||
input:hover:not(:disabled) { border-color: #c5c5c5; }
|
||||
input:focus {
|
||||
border-color: #929292;
|
||||
box-shadow: 0 0 0 3px #eeeeee, 0 1px 2px #00000003;
|
||||
}
|
||||
input:disabled { background: #f4f4f4; }
|
||||
button {
|
||||
min-width: 62px;
|
||||
padding: 0 15px;
|
||||
background: #303030;
|
||||
border: 1px solid #292929;
|
||||
color: #fafafa;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
box-shadow: inset 0 1px 0 #ffffff0d, 0 1px 2px #0000000c;
|
||||
transition: background .15s, border-color .15s, transform .12s;
|
||||
}
|
||||
button:hover:not(:disabled) {
|
||||
background: #454545;
|
||||
border-color: #3c3c3c;
|
||||
}
|
||||
button:active:not(:disabled) { transform: translateY(1px); }
|
||||
button:disabled {
|
||||
background: #ececec;
|
||||
border-color: #e3e3e3;
|
||||
color: #949494;
|
||||
box-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: 3px solid #c9c9c9;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.hint {
|
||||
margin: 12px 0 0;
|
||||
font-size: 10.5px;
|
||||
color: #818181;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.message {
|
||||
margin: 12px 0 0;
|
||||
font-size: 11px;
|
||||
color: #5c5c5c;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.message.success { color: #5c5c5c; }
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
main { margin-top: -8vh; }
|
||||
input { font-size: 16px; }
|
||||
input, button { height: 44px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { transition: none !important; }
|
||||
}
|
||||
Reference in new issue
Block a user