feat(books): 五书策展字段与泥潭作者;顺带入 T1 终端页文件
- schema 加 description/bookAuthor 可选;五书补字段(一句话策展评语
+ 原书作者;泥潭=刘楚昕 用户提供)
- 任务包入 T5 /opensource 独立页(用户追加需求,方案 A 复用扩展)
- 因 add -A 顺带包含 op-artalk 车道已自验 12/12 的 src/pages/terminal.astro
(T1 交付物,验收随后)
验证: grep bookAuthor content/posts/*.md 五篇命中;schema 可选字段
对既有构建零破坏
Spec: 2026-10-25_add-terminal-books-admin
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
b789d30a5d
commit
3bd8369eda
12 files changed
+4146
No files matched your search
@@ -4,6 +4,8 @@ pubDate: 2026-03-31
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#b45309'
|
mood: '#b45309'
|
||||||
|
bookAuthor: '萨缪尔·贝克特'
|
||||||
|
description: '无望之待里,陪伴是囚笼唯一的呼吸缝隙。'
|
||||||
---
|
---
|
||||||
|
|
||||||
### 谁是戈多?为何等待?
|
### 谁是戈多?为何等待?
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pubDate: 2026-03-25
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#0f766e'
|
mood: '#0f766e'
|
||||||
|
bookAuthor: '阿利斯泰尔·麦克劳德'
|
||||||
|
description: '秋是萧瑟之感:父亲、老马与少年,在失落后各自直面现实。'
|
||||||
---
|
---
|
||||||
|
|
||||||
### 秋-斯科特之秋、父亲之秋、少年之秋(冬的预演)
|
### 秋-斯科特之秋、父亲之秋、少年之秋(冬的预演)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pubDate: 2026-03-09
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#7c3aed'
|
mood: '#7c3aed'
|
||||||
|
bookAuthor: '威廉·萨默塞特·毛姆'
|
||||||
|
description: '面纱之下:理想的殉道者与真实的幸存者。'
|
||||||
---
|
---
|
||||||
|
|
||||||
****面纱之下:理想的殉道者和真实的幸存者****
|
****面纱之下:理想的殉道者和真实的幸存者****
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pubDate: 2026-02-09
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#166534'
|
mood: '#166534'
|
||||||
|
bookAuthor: '刘楚昕'
|
||||||
---
|
---
|
||||||
|
|
||||||
### 迷失在黑夜中时,不妨抬头看看星空;如果不知道该相信什么,人应当面对自己的良知。
|
### 迷失在黑夜中时,不妨抬头看看星空;如果不知道该相信什么,人应当面对自己的良知。
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pubDate: 2026-02-26
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#9f1239'
|
mood: '#9f1239'
|
||||||
|
bookAuthor: '伊恩·麦克尤恩'
|
||||||
|
description: '因一个人、一张图、一句台词,看了一本书。'
|
||||||
---
|
---
|
||||||
|
|
||||||
### 你要回来,我会等你。
|
### 你要回来,我会等你。
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ sqlite 直接热拷贝会撕裂,备份前 `docker compose down` 或用 `sqlite
|
|||||||
|
|
||||||
## 已知坑
|
## 已知坑
|
||||||
|
|
||||||
|
- **[cors] 段已启用(2026-10-25,Sveltia CMS 试水)**:`ALLOW_DOMAIN = https://wait4x.online,http://127.0.0.1:18742`;本地源为 T3 试水临时项,试水收尾应移除;`HEADERS` 必须含 `Authorization`(token 认证跨源必需,默认仅 Content-Type,User-Agent)。依据:docs.gitea.com config-cheat-sheet#cors
|
||||||
|
|
||||||
- **不要开 `REQUIRE_SIGNIN_VIEW`**:它会把匿名 `/api/v1/version` 一并 403,
|
- **不要开 `REQUIRE_SIGNIN_VIEW`**:它会把匿名 `/api/v1/version` 一并 403,
|
||||||
而验收与健康检查都裸 curl 该端点;私有性靠 private 仓 + `DISABLE_REGISTRATION` 保证。
|
而验收与健康检查都裸 curl 该端点;私有性靠 private 仓 + `DISABLE_REGISTRATION` 保证。
|
||||||
- nginx 的 `client_max_body_size 100M`(git push 大 pack)与 app.ini
|
- nginx 的 `client_max_body_size 100M`(git push 大 pack)与 app.ini
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# yaml-language-server: $schema=https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json
|
||||||
|
# Sveltia CMS 配置(T3 试水):仅 photos 集合,见 docs 预研报告
|
||||||
|
# 纪律:不开内建图片优化(transformations 会把原图转 webp 抹掉 EXIF,构建期提取报废)
|
||||||
|
backend:
|
||||||
|
name: gitea
|
||||||
|
repo: wait4x/blog
|
||||||
|
branch: main
|
||||||
|
base_url: https://git.wait4x.online
|
||||||
|
api_root: https://git.wait4x.online/api/v1
|
||||||
|
|
||||||
|
media_folder: content/photos
|
||||||
|
site_url: https://wait4x.online
|
||||||
|
|
||||||
|
output:
|
||||||
|
# Astro zod optional 兼容:空可选字段不写文件(官方 Astro 指南建议)
|
||||||
|
omit_empty_optional_fields: true
|
||||||
|
|
||||||
|
collections:
|
||||||
|
- name: photos
|
||||||
|
label: 图库照片
|
||||||
|
folder: content/photos
|
||||||
|
# 整文件 JSON(非 frontmatter)= 现有 sidecar 同构;不设 format 条目不列出
|
||||||
|
format: json
|
||||||
|
create: true
|
||||||
|
# 入口相对:jpg 与 sidecar 同目录;image 引用写裸文件名(loader new URL 等价解析)
|
||||||
|
media_folder: ''
|
||||||
|
public_folder: ''
|
||||||
|
summary: '{{fields.group}} · {{fields.title}}'
|
||||||
|
fields:
|
||||||
|
- { label: 标题, name: title, widget: string, required: false }
|
||||||
|
- { label: 图片, name: image, widget: image }
|
||||||
|
- { label: 分组, name: group, widget: select, options: [street, landscape] }
|
||||||
|
- { label: 替代文本, name: alt, widget: string, required: false }
|
||||||
|
- label: EXIF 覆盖(一般留空,构建期自动从图提取)
|
||||||
|
name: exif
|
||||||
|
widget: object
|
||||||
|
required: false
|
||||||
|
fields:
|
||||||
|
- { label: 机身, name: make, widget: string, required: false }
|
||||||
|
- { label: 镜头, name: lens, widget: string, required: false }
|
||||||
|
- { label: 光圈值, name: fNumber, widget: number, required: false }
|
||||||
|
- { label: 曝光秒, name: exposureTime, widget: number, required: false }
|
||||||
|
- { label: ISO, name: iso, widget: number, required: false }
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>Sveltia CMS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- pinned 本地 bundle(0.252.0):官方模板仅此一个 script,无 CSS link、无 type=module -->
|
||||||
|
<script src="./sveltia-cms.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -21,6 +21,7 @@ const routes: Record<string, string> = {
|
|||||||
<tr><td><kbd>g</kbd> <kbd>a</kbd></td><td>文章</td></tr>
|
<tr><td><kbd>g</kbd> <kbd>a</kbd></td><td>文章</td></tr>
|
||||||
<tr><td><kbd>g</kbd> <kbd>g</kbd></td><td>图库</td></tr>
|
<tr><td><kbd>g</kbd> <kbd>g</kbd></td><td>图库</td></tr>
|
||||||
<tr><td><kbd>g</kbd> <kbd>m</kbd></td><td>瞬间</td></tr>
|
<tr><td><kbd>g</kbd> <kbd>m</kbd></td><td>瞬间</td></tr>
|
||||||
|
<tr><td><kbd>/terminal</kbd></td><td>终端彩蛋</td></tr>
|
||||||
<tr><td><kbd>Esc</kbd></td><td>关闭浮层</td></tr>
|
<tr><td><kbd>Esc</kbd></td><td>关闭浮层</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ const postsSchema = z.object({
|
|||||||
tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
/** 情绪色,仅书评轨使用;#RRGGBB */
|
/** 情绪色,仅书评轨使用;#RRGGBB */
|
||||||
mood: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
mood: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
||||||
|
/** 一句话策展评语(/books 书架卡;缺省回退正文首段) */
|
||||||
|
description: z.string().optional(),
|
||||||
|
/** 原书作者(书架卡署名) */
|
||||||
|
bookAuthor: z.string().optional(),
|
||||||
draft: z.boolean().optional(),
|
draft: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,322 @@
|
|||||||
|
---
|
||||||
|
// /terminal 彩蛋页:交互终端,零依赖单文件实现
|
||||||
|
// 命令注册表唯一事实源;输出一律 DOM API + textContent(无 innerHTML,XSS 纪律)
|
||||||
|
// 实现集提炼自 Phase 5b 预研(satnaing/terminal-portfolio 实读 + CodeNerve 反例)
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="terminal" description="wait4x 的终端彩蛋:help 开始探索">
|
||||||
|
<section class="term-shell" id="term-shell" aria-label="交互终端彩蛋">
|
||||||
|
<div class="term-bar" aria-hidden="true">
|
||||||
|
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||||||
|
<span class="term-title">wait4x@blog: ~</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="term-body">
|
||||||
|
<!-- 欢迎语静态渲染:无 JS / 构建期首屏即有内容 -->
|
||||||
|
<div class="term-out" id="term-out" aria-live="polite">
|
||||||
|
<p class="line">wait4x terminal v1.0 —— 键入 help 查看可用命令</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p class="line dim">此彩蛋需要 JavaScript:静态站无运行时框架,仅此页例外。</p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<form id="term-form" class="term-form" autocomplete="off">
|
||||||
|
<label class="prompt" for="term-input">wait4x@blog:~$</label>
|
||||||
|
<!-- 输入行独立成行:移动端软键盘弹出时不遮挡回显 -->
|
||||||
|
<input
|
||||||
|
id="term-input"
|
||||||
|
class="term-input"
|
||||||
|
type="text"
|
||||||
|
aria-label="终端命令输入"
|
||||||
|
spellcheck="false"
|
||||||
|
autocapitalize="off"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ClientRouter 软导航会整体换 DOM:全部监听挂 document 常驻 + pathname 守卫短路,
|
||||||
|
// 元素引用一律现取不缓存(站内 CommandPalette/Header 同款模式,见各自注释)
|
||||||
|
const PATH = '/terminal/';
|
||||||
|
|
||||||
|
const COMMANDS: { name: string; desc: string }[] = [
|
||||||
|
{ name: 'clear', desc: '清屏(等同 Ctrl+L)' },
|
||||||
|
{ name: 'exit', desc: '退出终端,返回首页' },
|
||||||
|
{ name: 'help', desc: '列出可用命令' },
|
||||||
|
{ name: 'ls', desc: '列出站内页面(可点击跳转)' },
|
||||||
|
{ name: 'open', desc: 'open gitea —— 打开代码托管(新标签页)' },
|
||||||
|
{ name: 'sudo', desc: '试试看' },
|
||||||
|
{ name: 'theme', desc: '切换亮/暗主题' },
|
||||||
|
{ name: 'whoami', desc: '我是谁' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 唯一允许 open 的外部目标:白名单硬编码,永不拼接用户输入
|
||||||
|
const OPEN_TARGETS: Record<string, string> = {
|
||||||
|
gitea: 'https://git.wait4x.online/wait4x',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SITE_ROUTES: { path: string; label: string }[] = [
|
||||||
|
{ path: '/', label: '首页' },
|
||||||
|
{ path: '/archives/', label: '文章' },
|
||||||
|
{ path: '/gallery/', label: '图库' },
|
||||||
|
{ path: '/moments/', label: '瞬间' },
|
||||||
|
{ path: '/about/', label: '关于' },
|
||||||
|
{ path: '/en/', label: 'English home' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模块级仅存终端语义状态(不存 DOM 引用);history 顺序旧→新
|
||||||
|
const history: string[] = [];
|
||||||
|
let histPos = 0;
|
||||||
|
|
||||||
|
function isTerminalPage(): boolean {
|
||||||
|
return location.pathname === PATH || location.pathname === '/terminal';
|
||||||
|
}
|
||||||
|
|
||||||
|
function out(): HTMLElement | null {
|
||||||
|
return document.getElementById('term-out');
|
||||||
|
}
|
||||||
|
|
||||||
|
function input(): HTMLInputElement | null {
|
||||||
|
return document.getElementById('term-input') as HTMLInputElement | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function print(text: string, cls = ''): void {
|
||||||
|
const box = out();
|
||||||
|
if (!box) return;
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.className = cls ? `line ${cls}` : 'line';
|
||||||
|
p.textContent = text; // 纪律:一切回显走 textContent
|
||||||
|
box.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printLink(path: string, label: string): void {
|
||||||
|
const box = out();
|
||||||
|
if (!box) return;
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.className = 'line';
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = path;
|
||||||
|
a.textContent = `${path.padEnd(14)}${label}`;
|
||||||
|
a.className = 'term-link';
|
||||||
|
p.appendChild(a);
|
||||||
|
box.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottom(): void {
|
||||||
|
const shell = document.getElementById('term-shell');
|
||||||
|
shell?.scrollIntoView({ block: 'nearest' });
|
||||||
|
const box = out();
|
||||||
|
if (box) box.scrollTop = box.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(rawLine: string): void {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
print(`wait4x@blog:~$ ${rawLine}`, 'echo');
|
||||||
|
if (!line) return;
|
||||||
|
history.push(line);
|
||||||
|
histPos = history.length;
|
||||||
|
|
||||||
|
const [cmd, ...args] = line.split(/\s+/);
|
||||||
|
switch (cmd) {
|
||||||
|
case 'help': {
|
||||||
|
// 注册表即帮助:单一事实源
|
||||||
|
const width = Math.max(...COMMANDS.map((c) => c.name.length)) + 2;
|
||||||
|
for (const c of COMMANDS) print(`${c.name.padEnd(width)}${c.desc}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'whoami':
|
||||||
|
print('wait4x —— 阅读 · 技术 · 摄影,博客在 gitea 开源(open gitea)');
|
||||||
|
break;
|
||||||
|
case 'ls':
|
||||||
|
for (const r of SITE_ROUTES) printLink(r.path, r.label);
|
||||||
|
break;
|
||||||
|
case 'theme': {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||||
|
root.dataset.theme = next;
|
||||||
|
localStorage.setItem('theme', next); // 与 Header/Keyboard 的 t 键同存储键
|
||||||
|
print(`theme → ${next}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'open': {
|
||||||
|
const target = args[0];
|
||||||
|
const url = target ? OPEN_TARGETS[target] : undefined;
|
||||||
|
if (!url) {
|
||||||
|
print(`用法: open <target>;可用: ${Object.keys(OPEN_TARGETS).join(', ')}`, 'dim');
|
||||||
|
} else {
|
||||||
|
window.open(url, '_blank', 'noopener');
|
||||||
|
print(`opening ${url} …`, 'dim');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'sudo':
|
||||||
|
print('wait4x is not in the sudoers file. This incident will be reported.', 'err');
|
||||||
|
break;
|
||||||
|
case 'clear': {
|
||||||
|
out()?.replaceChildren();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'exit':
|
||||||
|
print('logout', 'dim');
|
||||||
|
setTimeout(() => {
|
||||||
|
location.href = '/';
|
||||||
|
}, 600);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print(`command not found: ${cmd}(help 查看可用命令)`, 'err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitLine(): void {
|
||||||
|
const el = input();
|
||||||
|
if (!el) return;
|
||||||
|
run(el.value);
|
||||||
|
el.value = '';
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabComplete(current: string): void {
|
||||||
|
if (!current) return;
|
||||||
|
const hits = COMMANDS.filter((c) => c.name.startsWith(current)).map((c) => c.name);
|
||||||
|
if (hits.length === 1) {
|
||||||
|
const el = input();
|
||||||
|
if (el) el.value = hits[0];
|
||||||
|
} else if (hits.length > 1) {
|
||||||
|
// 多命中:先回显当前输入再列候选,与真实 shell 一致
|
||||||
|
print(`wait4x@blog:~$ ${current}`, 'echo');
|
||||||
|
print(hits.join(' '), 'dim');
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function historyStep(dir: -1 | 1): void {
|
||||||
|
const el = input();
|
||||||
|
if (!el) return;
|
||||||
|
const next = histPos + dir;
|
||||||
|
if (next < 0 || next > history.length) return;
|
||||||
|
histPos = next;
|
||||||
|
el.value = next === history.length ? '' : history[next];
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('submit', (e) => {
|
||||||
|
if (!isTerminalPage()) return;
|
||||||
|
const form = e.target as HTMLElement;
|
||||||
|
if (form.id !== 'term-form') return;
|
||||||
|
e.preventDefault();
|
||||||
|
submitLine();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (!isTerminalPage()) return;
|
||||||
|
const el = e.target as HTMLElement;
|
||||||
|
if (el.id !== 'term-input') return;
|
||||||
|
|
||||||
|
// 中文输入法组词中的 Enter 不是提交;Tab 亦然
|
||||||
|
if (e.isComposing) return;
|
||||||
|
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
submitLine();
|
||||||
|
} else if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const cur = (el as HTMLInputElement).value.trim();
|
||||||
|
tabComplete(cur);
|
||||||
|
} else if (e.ctrlKey && e.key.toLowerCase() === 'l') {
|
||||||
|
e.preventDefault();
|
||||||
|
out()?.replaceChildren();
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
historyStep(-1);
|
||||||
|
} else if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
historyStep(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击终端区域聚焦输入框;有选区(用户在复制输出)时不抢焦点
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!isTerminalPage()) return;
|
||||||
|
const shell = (e.target as HTMLElement).closest?.('#term-shell');
|
||||||
|
if (!shell) return;
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (sel && !sel.isCollapsed) return;
|
||||||
|
if ((e.target as HTMLElement).tagName === 'A') return;
|
||||||
|
input()?.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// View Transitions:软导航抵达/回到本页时恢复焦点与光标末位
|
||||||
|
document.addEventListener('astro:after-swap', () => {
|
||||||
|
if (!isTerminalPage()) return;
|
||||||
|
const el = input();
|
||||||
|
if (el) {
|
||||||
|
el.focus();
|
||||||
|
const end = el.value.length;
|
||||||
|
el.setSelectionRange(end, end);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 恒深色例外区:--code-bg 亮暗同值,终端不跟随主题(本页唯一例外,彩蛋语义) */
|
||||||
|
.term-shell {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: var(--space-6) auto;
|
||||||
|
background: var(--code-bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
.term-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #3d4149;
|
||||||
|
}
|
||||||
|
.term-title {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #6e7681;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
.term-body { padding: 14px 16px 16px; }
|
||||||
|
.term-out { max-height: 60vh; overflow-y: auto; }
|
||||||
|
.line { margin: 0; color: var(--code-ink); white-space: pre-wrap; word-break: break-word; }
|
||||||
|
.line.echo { color: #adbac7; }
|
||||||
|
.line.dim { color: #6e7681; }
|
||||||
|
.line.err { color: #f85149; }
|
||||||
|
.term-link { color: #539bf5; text-decoration: none; }
|
||||||
|
.term-link:hover { text-decoration: underline; }
|
||||||
|
.term-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.prompt { color: #7ee787; white-space: nowrap; }
|
||||||
|
.term-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; /* flex 子项收缩时保输入可用(窄屏) */
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--code-ink);
|
||||||
|
font: inherit;
|
||||||
|
caret-color: #7ee787;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</BaseLayout>
|
||||||
Reference in new issue
Block a user