docs: update changelog and documentation for version 1.0.7
- Added new features including `Background Image` customization and `Backspace Mode` for Telnet and Serial sessions. - Improved UI for font selection and OTP input layout. - Fixed CSP asset protocol handling for local assets. - Enhanced performance by loading system fonts asynchronously. - Updated README and guides to reflect new features and improvements.
This commit is contained in:
1 parent
5616b9a838
commit
3709f202b1
16 files changed
+634
-91
No files matched your search
+40
-8
@@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
This changelog is inferred from release bump commits in git history (for example `chore: bump version to vX.Y.Z`) and grouped by Conventional Commit type.
|
||||
|
||||
## [1.0.7] - 2026-05-21
|
||||
|
||||
### Changed
|
||||
|
||||
- **appearance:** Improve font selection UI and show a `Loading system fonts...` state while system fonts are being discovered.
|
||||
- **otp:** Improve OTP input slot layout and OTP code panel responsiveness on narrower screens.
|
||||
- **profiles:** Add multiple Cargo build profiles for debugging and release workflows.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **csp:** Correct CSP asset protocol handling so local assets such as background images load reliably.
|
||||
|
||||
### Performance
|
||||
|
||||
- **appearance:** Load system fonts asynchronously to keep the Appearance tab responsive.
|
||||
|
||||
## [1.0.6] - 2026-05-21
|
||||
|
||||
### Added
|
||||
|
||||
- **appearance:** Add `Background Image` customization for the main window, including `Image Sizing`, `Image Opacity`, and `Background Content Opacity` controls.
|
||||
- **sessions:** Add `Backspace Mode` selection for Telnet and Serial sessions with `Ctrl+H (BS)` and `DEL (0x7F)` options.
|
||||
|
||||
### Changed
|
||||
|
||||
- **resource-monitor:** Refresh the resource monitor with clearer cards and improved visual hierarchy.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **shortcuts:** Prevent Shift-modified terminal input from being mistaken for application shortcuts, restoring uppercase input such as `Shift+C`, `Shift+V`, and `Shift+X`.
|
||||
|
||||
## [1.0.5] - 2026-05-19
|
||||
|
||||
### Added
|
||||
|
||||
- **ai:** Capture AI command execution events and render inline terminal output during agent-driven workflows.
|
||||
- **ai:** Add the `Terminal Output Lines` setting to control how many inline output lines are shown for AI-executed commands.
|
||||
- **terminal:** Add AI Execution Profile selection to terminal session forms.
|
||||
- **window:** Improve main-window modal management so child windows keep the workspace in a clearer modal state.
|
||||
|
||||
## [1.0.4] - 2026-05-19
|
||||
|
||||
### Added
|
||||
@@ -75,14 +115,6 @@ This changelog is inferred from release bump commits in git history (for example
|
||||
- **docs:** Update changelog with new releases and enhancements.
|
||||
- **ci:** Update Docusaurus dependencies and remove optional Umami config.
|
||||
|
||||
## [1.0.4] - 2026-05-19
|
||||
|
||||
## [1.0.3] - 2026-05-18
|
||||
|
||||
## [1.0.2] - 2026-05-17
|
||||
|
||||
## [1.0.1] - 2026-05-16
|
||||
|
||||
## [1.0.0] - 2026-05-06
|
||||
|
||||
## [0.9.0] - 2026-04-30
|
||||
|
||||
@@ -47,7 +47,9 @@ NyaTerm includes an AI Assistant panel for command generation, terminal output e
|
||||
|
||||
- **Ask mode** for one-off help such as generating commands, explaining selected output, and analyzing errors
|
||||
- **Agent mode** for multi-step work using an observe-decide-run loop against the active terminal session
|
||||
- **Recent-output actions** so you can ask AI to explain the latest terminal output without manually copying it
|
||||
- **Structured command cards** with risk levels, execution controls, and optional save-to-quick-command support
|
||||
- **Inline terminal capture** during Agent execution with configurable `Terminal Output Lines`
|
||||
- **Session mentions** with `@` to bring other terminal sessions into the AI context
|
||||
- **Provider management** for built-in providers and custom OpenAI-compatible endpoints
|
||||
- **Risk control** for high-impact commands, including approval gates and safer alternatives
|
||||
@@ -88,6 +90,7 @@ NyaTerm is built for people who move between servers, local commands, devices, a
|
||||
- SSH, Local Terminal, Telnet, and Serial session support
|
||||
- Multi-tab workspace with horizontal and vertical pane splits
|
||||
- Saved connections with folders, icons, metadata, duplication, reconnect, and import flows
|
||||
- Main-window `Background Image` customization with `cover` / `contain` / `stretch` / `tile` sizing and adjustable `Background Content Opacity`
|
||||
- Left and right activity bars for file explorer, network, Security/Auth, Sync & Backup, AI Assistant, saved connections, active sessions, command history, and resource monitoring
|
||||
- Child windows for settings, new-session creation, quick-command editing, and auto-upload prompts
|
||||
- Tray support with optional minimize-to-tray behavior
|
||||
@@ -99,10 +102,11 @@ NyaTerm is built for people who move between servers, local commands, devices, a
|
||||
- Optional line-number and timestamp gutter
|
||||
- Optional action links for IPv4 addresses, `host:port`, and archive filenames
|
||||
- Optional keyword highlighting with built-in presets and custom rules
|
||||
- AI shortcuts for explaining recent output, plus inline Agent command output with configurable `Terminal Output Lines`
|
||||
- Large-output protection, configurable scrollback, SSH keep-alive, and session recording
|
||||
- Online search and translation from selected terminal text
|
||||
- Zmodem file transfer support directly from the terminal
|
||||
- Customizable keyboard shortcuts for terminal and UI actions
|
||||
- Customizable keyboard shortcuts for terminal and UI actions, including `Backspace Mode` selection for Telnet and Serial sessions
|
||||
|
||||
## SFTP and File Workflows
|
||||
|
||||
@@ -147,6 +151,18 @@ Manage SSH, local shell, Telnet, and Serial sessions inside one tabbed and split
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## Appearance and Background Image
|
||||
|
||||
Use a local wallpaper behind the main window, tune `Image Sizing`, `Image Opacity`, and `Background Content Opacity`, and keep child windows readable with solid surfaces.
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./docs-site/static/img/home/cover-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="./docs-site/static/img/home/cover-light.png">
|
||||
<img alt="NyaTerm background image customization" src="./docs-site/static/img/home/cover-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## Terminal Enhancements
|
||||
|
||||
Use command history, search, translation, action links, timestamps, keyword highlighting, and large-output protection in the terminal.
|
||||
@@ -217,8 +233,8 @@ Download installers from [nyaterm.app](https://nyaterm.app) or the [Releases](ht
|
||||
|------|-------------|-------|
|
||||
| SSH | Linux / Unix remote servers | Supports SFTP, OTP, resource monitor, proxy, jump host, and tunnels |
|
||||
| Local Terminal | Local shell workflows | Uses your local shell path and working directory |
|
||||
| Telnet | Legacy network devices or lab systems | Lightweight terminal session without SSH-only features |
|
||||
| Serial | Routers, boards, embedded devices | Configurable port, baud rate, data bits, parity, and stop bits |
|
||||
| Telnet | Legacy network devices or lab systems | Lightweight terminal session without SSH-only features, with `Backspace Mode` for `Ctrl+H (BS)` or `DEL (0x7F)` |
|
||||
| Serial | Routers, boards, embedded devices | Configurable port, baud rate, data bits, parity, stop bits, and `Backspace Mode` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+19
-3
@@ -47,7 +47,9 @@ NyaTerm 内置 AI Assistant 面板,可用于生成命令、解释终端输出
|
||||
|
||||
- **Ask 模式**:适合单次问答,例如生成命令、解释选中输出、分析错误
|
||||
- **Agent 模式**:基于当前活跃终端会话,按“观察、决策、执行”的循环完成多步任务
|
||||
- **最近输出入口**:可以直接让 AI 解释最近的终端输出,不必手动复制
|
||||
- **结构化命令卡片**:展示风险等级、执行控制,并可保存为快捷命令
|
||||
- **终端内联输出捕获**:Agent 执行命令时可把输出写回终端,并通过 `Terminal Output Lines` 控制显示行数
|
||||
- **会话提及**:输入 `@` 可把其他终端会话纳入当前 AI 上下文
|
||||
- **Provider 管理**:支持内置 provider 和自定义 OpenAI Compatible 端点
|
||||
- **风险控制**:对高影响命令提供审批门槛和更安全的替代建议
|
||||
@@ -89,6 +91,7 @@ NyaTerm 适合每天在服务器、本地命令、设备调试和配置文件之
|
||||
- 支持 SSH、本地终端、Telnet 和串口会话
|
||||
- 多标签页工作区,标签页内支持横向和纵向分屏
|
||||
- 已保存连接支持分组、图标、元数据、复制、重连和导入
|
||||
- 主窗口支持 `Background Image` 背景图自定义,可调 `cover` / `contain` / `stretch` / `tile` 以及 `Background Content Opacity`
|
||||
- 左右活动栏覆盖文件浏览器、网络、Security/Auth、Sync & Backup、AI Assistant、已保存连接、活动会话、命令历史和资源监控
|
||||
- 设置、新建连接、快捷命令编辑、自动上传提示使用独立子窗口
|
||||
- 支持托盘与关闭时最小化到托盘
|
||||
@@ -100,10 +103,11 @@ NyaTerm 适合每天在服务器、本地命令、设备调试和配置文件之
|
||||
- 可选行号和时间戳 gutter
|
||||
- 可选动作链接,识别 IPv4、`host:port` 和压缩包文件名
|
||||
- 可选关键词高亮,支持内置预设和自定义规则
|
||||
- 可直接让 AI 解释最近输出,并通过 `Terminal Output Lines` 控制 Agent 命令的终端内联输出
|
||||
- 大输出保护、可配置回滚缓冲区、SSH Keep-Alive 和会话录制
|
||||
- 可从选中文本发起在线搜索和翻译
|
||||
- 支持直接在终端中使用 Zmodem 进行文件传输
|
||||
- 支持自定义终端与界面操作的键盘快捷键
|
||||
- 支持自定义终端与界面操作的键盘快捷键,Telnet / 串口会话还支持 `Backspace Mode`
|
||||
|
||||
## SFTP 与文件工作流
|
||||
|
||||
@@ -148,6 +152,18 @@ NyaTerm 适合每天在服务器、本地命令、设备调试和配置文件之
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## 外观与背景图
|
||||
|
||||
为主窗口设置本地壁纸,并通过 `Image Sizing`、`Image Opacity` 和 `Background Content Opacity` 平衡视觉效果与面板可读性。
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./docs-site/static/img/home/cover-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="./docs-site/static/img/home/cover-light.png">
|
||||
<img alt="NyaTerm 背景图自定义" src="./docs-site/static/img/home/cover-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## 终端增强
|
||||
|
||||
在终端中使用命令历史、搜索、翻译、动作链接、时间戳、关键词高亮和大输出保护。
|
||||
@@ -218,8 +234,8 @@ NyaTerm 适合每天在服务器、本地命令、设备调试和配置文件之
|
||||
|------|----------|------|
|
||||
| SSH | Linux / Unix 远程服务器 | 支持 SFTP、OTP、资源监控、代理、跳板机和隧道 |
|
||||
| 本地终端 | 本地 Shell 工作流 | 使用本机 Shell 路径和工作目录 |
|
||||
| Telnet | 旧设备或实验环境 | 轻量终端会话,不包含 SSH 专属能力 |
|
||||
| 串口 | 路由器、开发板、嵌入式设备 | 可配置端口、波特率、数据位、校验位和停止位 |
|
||||
| Telnet | 旧设备或实验环境 | 轻量终端会话,不包含 SSH 专属能力,并支持 `Backspace Mode` 在 `Ctrl+H (BS)` 与 `DEL (0x7F)` 间切换 |
|
||||
| 串口 | 路由器、开发板、嵌入式设备 | 可配置端口、波特率、数据位、校验位、停止位和 `Backspace Mode` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ Agent 模式的特点:
|
||||
- 需要一个活跃的终端会话
|
||||
- 每一步命令都会显示执行状态(运行中、已完成、失败、超时、需要审批、被安全策略阻止)
|
||||
- 可在 **设置 → AI → Agent Settings** 中配置最大步数和单步超时
|
||||
- 可通过 `Terminal Output Lines` 控制 AI 执行命令时在终端中内联展示多少行输出
|
||||
- 高风险命令仍然需要人工审批后才能执行
|
||||
|
||||
## 会话管理
|
||||
@@ -71,6 +72,14 @@ AI 返回的命令以结构化卡片展示,包含:
|
||||
|
||||
当 AI 检测到高风险命令时,可能会同时提供更安全的替代命令供选择。
|
||||
|
||||
## 最近输出与终端内联输出
|
||||
|
||||
AI 助手不仅能处理选中文本,也可以直接围绕“刚刚发生了什么”来工作。
|
||||
|
||||
- 在终端右键菜单中使用 **解释最近输出**,可以把当前会话最近的终端输出直接发送给 AI
|
||||
- 在 Agent 模式下,AI 执行命令时会捕获命令执行事件,并把摘要后的输出以内联片段形式反馈到终端工作流附近
|
||||
- `Terminal Output Lines` 决定这类终端内联反馈最多展示多少行;设为 `0` 则关闭
|
||||
|
||||
## 推理内容
|
||||
|
||||
如果使用的模型支持推理通道(如 DeepSeek R1、QwQ 等),AI 助手会在回复中展示推理过程。推理内容默认折叠,可以展开查看 AI 的思考链路。
|
||||
|
||||
@@ -19,8 +19,8 @@ NyaTerm 不只是 SSH 客户端,而是一个把多类终端工作流放到同
|
||||
|----------|----------|----------------|
|
||||
| SSH | Linux / Unix 服务器远程运维 | SFTP、OTP、资源监控、代理、跳板机、隧道 |
|
||||
| 本地终端 | 本地 shell、脚本调试、构建命令 | 共享同一套终端 UI、命令历史、分屏 |
|
||||
| Telnet | 旧设备、实验环境、兼容性排障 | 终端工作区能力,但不包含 SSH 专属特性 |
|
||||
| 串口 | 路由器、交换机、板卡、嵌入式调试口 | 串口参数配置、终端工作区能力 |
|
||||
| Telnet | 旧设备、实验环境、兼容性排障 | 终端工作区能力,支持 `Backspace Mode`,但不包含 SSH 专属特性 |
|
||||
| 串口 | 路由器、交换机、板卡、嵌入式调试口 | 串口参数配置、`Backspace Mode` 与终端工作区能力 |
|
||||
|
||||
## SSH
|
||||
|
||||
@@ -79,6 +79,8 @@ Telnet 会话适合:
|
||||
|
||||
如果你的目标只是“快速打开一个传统远程终端”,Telnet 会更直接。
|
||||
|
||||
对于依赖退格键兼容性的设备,Telnet 会话还提供 `Backspace Mode`,可以在 `Ctrl+H (BS)` 和 `DEL (0x7F)` 之间切换。
|
||||
|
||||
## 串口
|
||||
|
||||
串口会话适合连接:
|
||||
@@ -94,6 +96,7 @@ Telnet 会话适合:
|
||||
- 数据位
|
||||
- 校验位
|
||||
- 停止位
|
||||
- `Backspace Mode`
|
||||
|
||||
串口会话同样可以放进 NyaTerm 的标签页与分屏工作区中,适合边看一个串口输出,边在另一个 SSH 或本地终端里执行命令。
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ NyaTerm 在右侧面板中内置了 **AI Assistant**,并且可以从终端上
|
||||
### 常见用法
|
||||
|
||||
- **生成命令**:告诉它你想完成什么,由它生成命令卡片
|
||||
- **解释最近输出**:针对当前会话最近的终端输出做说明
|
||||
- **解释最近输出**:针对当前会话最近的终端输出做说明,不必手动复制日志片段
|
||||
- **解释选中内容**:对选中的日志、报错或片段做解释
|
||||
- **分析错误**:把错误上下文交给 AI 生成修复建议
|
||||
- **修复选中内容**:从选中报错推导下一步命令
|
||||
@@ -81,6 +81,16 @@ AI 返回的命令可以以结构化卡片形式展示,并带有风险等级
|
||||
- 是否允许把生成命令保存为快捷命令
|
||||
- 是否记录 AI 历史
|
||||
|
||||
### 终端内联输出
|
||||
|
||||
当 Agent 模式执行命令时,NyaTerm 可以捕获命令执行事件,并把摘要后的输出以内联形式展示在终端工作流附近,方便你连续跟进多步操作的结果。
|
||||
|
||||
这个行为由 **设置 → AI → Agent Settings** 中的 `Terminal Output Lines` 控制:
|
||||
|
||||
- `Terminal Output Lines` 表示每次 AI 执行命令后,在终端中额外展示多少行输出
|
||||
- 设为 `0` 时,关闭终端内联输出展示
|
||||
- 这个设置不会影响真实命令本身的执行,只影响 AI 工作流中的附加可视化反馈
|
||||
|
||||
### Provider 与模型
|
||||
|
||||
AI 能力支持:
|
||||
|
||||
@@ -15,6 +15,17 @@ NyaTerm 支持对工作区外观做细粒度调整,包括 UI 主题、终端
|
||||
|
||||
如果你只是想快速切换主题,也可以使用顶部菜单中的 **View → Theme**。
|
||||
|
||||
## 背景图
|
||||
|
||||
在 **Settings → Appearance** 中,主窗口现在可以使用本地背景图。相关设置建议直接按界面里的真实名称理解:
|
||||
|
||||
- `Background Image`:选择主窗口背后的本地图片
|
||||
- `Image Sizing`:控制图片以 `cover`、`contain`、`stretch` 或 `tile` 的方式铺开
|
||||
- `Image Opacity`:控制背景图透过主题显示的强度
|
||||
- `Background Content Opacity`:控制工作区面板和内容表面的透光程度
|
||||
|
||||
这个能力只作用于主窗口工作区。设置页和其他子窗口仍然保持实色背景,避免在表单、弹窗和独立窗口里牺牲可读性。
|
||||
|
||||
## 字体与字号
|
||||
|
||||
在 **设置 → 外观** 中可以调整:
|
||||
@@ -31,6 +42,8 @@ NyaTerm 内置了这些常用字体:
|
||||
|
||||
同时也会读取系统已安装字体,供你加入字体回退链路。
|
||||
|
||||
系统字体现在是异步发现的,因此首次展开字体列表时,界面可能会短暂显示 `Loading system fonts...`。这表示应用正在后台整理系统字体,不影响你先选择内置字体或已有回退链路。
|
||||
|
||||
## 光标与连字
|
||||
|
||||
外观设置还支持这些终端细节:
|
||||
|
||||
@@ -173,6 +173,24 @@
|
||||
"在侧边区域查看会话列表、文件和资源信息": {
|
||||
"message": "View session lists, files, and resource information in side panels"
|
||||
},
|
||||
"外观": {
|
||||
"message": "Appearance"
|
||||
},
|
||||
"用背景图定制主窗口氛围": {
|
||||
"message": "Customize the workspace mood with a background image"
|
||||
},
|
||||
"主窗口支持本地背景图,可在保留面板可读性的同时营造更个性化的工作区视觉效果。": {
|
||||
"message": "The main window supports a local background image so you can create a more personal workspace look without sacrificing panel readability."
|
||||
},
|
||||
"使用本地图片作为主窗口的 Background Image": {
|
||||
"message": "Use a local image as the main window Background Image"
|
||||
},
|
||||
"通过 cover、contain、stretch 和 tile 控制图片铺法": {
|
||||
"message": "Control the image layout with cover, contain, stretch, and tile"
|
||||
},
|
||||
"分别调整 Image Opacity 和 Background Content Opacity": {
|
||||
"message": "Adjust Image Opacity and Background Content Opacity separately"
|
||||
},
|
||||
"终端": {
|
||||
"message": "Terminal"
|
||||
},
|
||||
@@ -245,6 +263,9 @@
|
||||
"支持 `.dgfy` 导入导出,并处理远端与本地版本冲突": {
|
||||
"message": "Supports `.dgfy` import/export and handles version conflicts between remote and local data"
|
||||
},
|
||||
"加载预览中": {
|
||||
"message": "Loading preview"
|
||||
},
|
||||
"支持 SSH、串口、本地 shell、SFTP 和配置备份的桌面终端客户端": {
|
||||
"message": "A desktop terminal client for SSH, serial, local shell, SFTP, and configuration backups"
|
||||
},
|
||||
|
||||
@@ -19,8 +19,8 @@ Understanding the differences helps explain why some panels or enhancements only
|
||||
|--------------|------------------|------------------|
|
||||
| SSH | Remote Linux / Unix administration | SFTP, OTP, resource monitoring, proxy, jump host, tunnels |
|
||||
| Local Terminal | Local shell work, scripts, builds | Shared terminal UI, command history, split panes |
|
||||
| Telnet | Legacy devices, lab environments, compatibility troubleshooting | Terminal workspace features, but not SSH-only features |
|
||||
| Serial | Routers, switches, boards, embedded debug ports | Serial port settings plus terminal workspace features |
|
||||
| Telnet | Legacy devices, lab environments, compatibility troubleshooting | Terminal workspace features with `Backspace Mode`, but not SSH-only features |
|
||||
| Serial | Routers, switches, boards, embedded debug ports | Serial port settings, `Backspace Mode`, and terminal workspace features |
|
||||
|
||||
## SSH
|
||||
|
||||
@@ -77,6 +77,8 @@ You still get NyaTerm's terminal workspace model, but not SSH-specific security
|
||||
|
||||
If your goal is simply to open a traditional remote terminal quickly, Telnet can be the more direct choice.
|
||||
|
||||
For devices that expect specific erase behavior, Telnet also exposes `Backspace Mode` so you can choose `Ctrl+H (BS)` or `DEL (0x7F)`.
|
||||
|
||||
## Serial
|
||||
|
||||
Serial sessions are useful for connecting to:
|
||||
@@ -92,6 +94,7 @@ When creating a serial session, you can configure:
|
||||
- Data bits
|
||||
- Parity
|
||||
- Stop bits
|
||||
- `Backspace Mode`
|
||||
|
||||
Serial sessions still live inside NyaTerm's tabbed and split workspace, so you can watch serial output in one pane while running commands in an SSH or local terminal pane.
|
||||
|
||||
|
||||
@@ -47,6 +47,27 @@ NyaTerm provides two related helpers for session workflows.
|
||||
|
||||
While typing, NyaTerm can suggest commands based on history. This is useful for repeated operational commands, build commands, and troubleshooting scripts.
|
||||
|
||||
## AI Assistant from the terminal
|
||||
|
||||
NyaTerm can invoke AI workflows directly from terminal context instead of forcing you to copy text into a separate tool.
|
||||
|
||||
### Common entry points
|
||||
|
||||
- **Explain recent output** when you want the latest terminal activity summarized immediately
|
||||
- **Explain selected text** for a highlighted log fragment or command result
|
||||
- **Analyze error** when terminal output already contains a failure you want help with
|
||||
- **Generate command** when you want AI to propose the next step from the current session context
|
||||
|
||||
### Inline terminal capture
|
||||
|
||||
During Agent execution, NyaTerm can capture AI command execution events and show a compact inline preview of command output close to the terminal workflow.
|
||||
|
||||
The amount of inline output is controlled by `Terminal Output Lines` in **Settings → AI → Agent Settings**:
|
||||
|
||||
- `Terminal Output Lines` sets how many output lines are shown after each AI-executed command
|
||||
- Set it to `0` to disable inline terminal previews
|
||||
- This setting changes the AI workflow feedback, not the underlying shell execution itself
|
||||
|
||||
## Optional terminal enhancements
|
||||
|
||||
These features are intentionally opt-in rather than enabled all at once.
|
||||
|
||||
@@ -15,6 +15,17 @@ In **Settings → Appearance**, you can configure these separately:
|
||||
|
||||
If you just want a quick theme switch, you can also use **View → Theme** from the top menu.
|
||||
|
||||
## Background image
|
||||
|
||||
In **Settings → Appearance**, the main window can now use a local wallpaper. The controls are easiest to follow by their exact UI names:
|
||||
|
||||
- `Background Image` — choose the local file rendered behind the main workspace
|
||||
- `Image Sizing` — choose how the image is shown with `cover`, `contain`, `stretch`, or `tile`
|
||||
- `Image Opacity` — control how strongly the wallpaper shows through the theme
|
||||
- `Background Content Opacity` — control how translucent workspace panels and content surfaces become
|
||||
|
||||
This only affects the main window workspace. Settings and child windows stay solid so forms, dialogs, and secondary windows remain readable.
|
||||
|
||||
## Fonts and font size
|
||||
|
||||
In **Settings → Appearance**, you can adjust:
|
||||
@@ -31,6 +42,8 @@ NyaTerm includes these built-in fonts:
|
||||
|
||||
System-installed fonts are also listed so you can extend the fallback chain.
|
||||
|
||||
System font discovery now runs asynchronously, so you may briefly see `Loading system fonts...` when opening the font picker. That simply means the app is still collecting installed fonts in the background.
|
||||
|
||||
## Cursor and ligatures
|
||||
|
||||
Appearance settings also expose terminal details such as:
|
||||
|
||||
@@ -9,6 +9,63 @@ export type ChangelogRelease = {
|
||||
};
|
||||
|
||||
const changelogReleasesEn: ChangelogRelease[] = [
|
||||
{
|
||||
version: '[1.0.7] - 2026-05-21',
|
||||
sections: [
|
||||
{
|
||||
title: 'Changed',
|
||||
items: [
|
||||
'**appearance:** Improve font selection UI and show a `Loading system fonts...` state while system fonts are being discovered.',
|
||||
'**otp:** Improve OTP input slot layout and OTP code panel responsiveness on narrower screens.',
|
||||
'**profiles:** Add multiple Cargo build profiles for debugging and release workflows.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Fixed',
|
||||
items: ['**csp:** Correct CSP asset protocol handling so local assets such as background images load reliably.'],
|
||||
},
|
||||
{
|
||||
title: 'Performance',
|
||||
items: ['**appearance:** Load system fonts asynchronously to keep the Appearance tab responsive.'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.6] - 2026-05-21',
|
||||
sections: [
|
||||
{
|
||||
title: 'Added',
|
||||
items: [
|
||||
'**appearance:** Add `Background Image` customization for the main window, including `Image Sizing`, `Image Opacity`, and `Background Content Opacity` controls.',
|
||||
'**sessions:** Add `Backspace Mode` selection for Telnet and Serial sessions with `Ctrl+H (BS)` and `DEL (0x7F)` options.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Changed',
|
||||
items: ['**resource-monitor:** Refresh the resource monitor with clearer cards and improved visual hierarchy.'],
|
||||
},
|
||||
{
|
||||
title: 'Fixed',
|
||||
items: [
|
||||
'**shortcuts:** Prevent Shift-modified terminal input from being mistaken for application shortcuts, restoring uppercase input such as `Shift+C`, `Shift+V`, and `Shift+X`.',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.5] - 2026-05-19',
|
||||
sections: [
|
||||
{
|
||||
title: 'Added',
|
||||
items: [
|
||||
'**ai:** Capture AI command execution events and render inline terminal output during agent-driven workflows.',
|
||||
'**ai:** Add the `Terminal Output Lines` setting to control how many inline output lines are shown for AI-executed commands.',
|
||||
'**terminal:** Add AI Execution Profile selection to terminal session forms.',
|
||||
'**window:** Improve main-window modal management so child windows keep the workspace in a clearer modal state.',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.4] - 2026-05-19',
|
||||
sections: [
|
||||
@@ -948,6 +1005,63 @@ const changelogReleasesEn: ChangelogRelease[] = [
|
||||
];
|
||||
|
||||
const changelogReleasesZhCN: ChangelogRelease[] = [
|
||||
{
|
||||
version: '[1.0.7] - 2026-05-21',
|
||||
sections: [
|
||||
{
|
||||
title: '变更',
|
||||
items: [
|
||||
'**appearance:** 改进字体选择界面,并在系统字体发现过程中显示 `Loading system fonts...` 加载状态。',
|
||||
'**otp:** 优化 OTP 输入槽位布局和 OTP 代码面板在窄屏下的响应式表现。',
|
||||
'**profiles:** 为调试和发布流程新增多个 Cargo 构建 profile。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '修复',
|
||||
items: ['**csp:** 修正 CSP 中的资源协议处理,确保背景图等本地资源可以稳定加载。'],
|
||||
},
|
||||
{
|
||||
title: '性能',
|
||||
items: ['**appearance:** 改为异步加载系统字体,保持外观设置页的响应速度。'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.6] - 2026-05-21',
|
||||
sections: [
|
||||
{
|
||||
title: '新增',
|
||||
items: [
|
||||
'**appearance:** 为主窗口新增 `Background Image` 自定义能力,支持 `Image Sizing`、`Image Opacity` 和 `Background Content Opacity` 控制。',
|
||||
'**sessions:** 为 Telnet 和串口会话新增 `Backspace Mode` 选项,可在 `Ctrl+H (BS)` 与 `DEL (0x7F)` 之间切换。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '变更',
|
||||
items: ['**resource-monitor:** 刷新资源监控面板的卡片样式和视觉层级。'],
|
||||
},
|
||||
{
|
||||
title: '修复',
|
||||
items: [
|
||||
'**shortcuts:** 修复 Shift 修饰键输入被误判为应用快捷键的问题,恢复 `Shift+C`、`Shift+V`、`Shift+X` 等大写输入。',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.5] - 2026-05-19',
|
||||
sections: [
|
||||
{
|
||||
title: '新增',
|
||||
items: [
|
||||
'**ai:** 捕获 AI 命令执行事件,并在 Agent 工作流中渲染终端内联输出。',
|
||||
'**ai:** 新增 `Terminal Output Lines` 设置,用于控制 AI 执行命令时显示的内联输出行数。',
|
||||
'**terminal:** 为终端会话表单新增 AI Execution Profile 选择。',
|
||||
'**window:** 改进主窗口模态管理,让子窗口触发时工作区的模态状态更清晰。',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '[1.0.4] - 2026-05-19',
|
||||
sections: [
|
||||
|
||||
@@ -468,7 +468,7 @@
|
||||
|
||||
.featureTabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||
gap: 0.55rem;
|
||||
padding: 0.45rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||
@@ -742,20 +742,29 @@
|
||||
padding: 0.65rem;
|
||||
}
|
||||
|
||||
.featurePreviewImageSlot {
|
||||
.featurePreviewStage {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(37, 99, 235, 0.14), transparent 52%),
|
||||
linear-gradient(180deg, rgba(226, 232, 240, 0.95) 0%, rgba(241, 245, 249, 0.94) 100%);
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.featurePreviewDark {
|
||||
display: none;
|
||||
[data-theme='dark'] .featurePreviewStage {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(143, 194, 255, 0.14), transparent 48%),
|
||||
linear-gradient(180deg, rgba(17, 23, 32, 0.98) 0%, rgba(22, 28, 36, 0.98) 100%);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewLight {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewDark {
|
||||
display: block;
|
||||
.featurePreviewPlaceholder {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
background:
|
||||
linear-gradient(120deg, rgba(255, 255, 255, 0.3) 8%, rgba(255, 255, 255, 0.68) 18%, rgba(255, 255, 255, 0.28) 33%),
|
||||
linear-gradient(180deg, rgba(241, 245, 249, 0.5), rgba(226, 232, 240, 0.72));
|
||||
}
|
||||
|
||||
.featurePreviewImage {
|
||||
@@ -765,6 +774,122 @@
|
||||
border-radius: 6px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
transition:
|
||||
filter 0.26s ease,
|
||||
opacity 0.26s ease,
|
||||
transform 0.26s ease;
|
||||
animation: featurePreviewImageFadeIn 0.32s ease both;
|
||||
}
|
||||
|
||||
.featurePreviewStageLoading .featurePreviewImage {
|
||||
filter: blur(12px) saturate(0.82);
|
||||
opacity: 0.38;
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
.featurePreviewLoadingOverlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(248, 250, 252, 0.24);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewLoadingOverlay {
|
||||
background: rgba(8, 12, 18, 0.28);
|
||||
}
|
||||
|
||||
.featurePreviewLoadingGlass {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
padding: 1rem 1.15rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.3);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.56);
|
||||
box-shadow: 0 22px 48px rgba(15, 23, 42, 0.14);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewLoadingGlass {
|
||||
border-color: rgba(148, 163, 184, 0.18);
|
||||
background: rgba(20, 27, 36, 0.62);
|
||||
box-shadow: 0 22px 48px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.featurePreviewSpinner {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border: 2px solid rgba(37, 99, 235, 0.16);
|
||||
border-top-color: #2563eb;
|
||||
border-radius: 999px;
|
||||
animation: featurePreviewSpinner 0.9s linear infinite;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewSpinner {
|
||||
border-color: rgba(143, 194, 255, 0.16);
|
||||
border-top-color: #8fc2ff;
|
||||
}
|
||||
|
||||
.featurePreviewLoadingText {
|
||||
color: #0f172a;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewLoadingText {
|
||||
color: #eef3f8;
|
||||
}
|
||||
|
||||
.featurePreviewLoadingShimmer {
|
||||
position: absolute;
|
||||
inset: -35%;
|
||||
background: linear-gradient(115deg, transparent 28%, rgba(255, 255, 255, 0.58) 50%, transparent 72%);
|
||||
opacity: 0.8;
|
||||
transform: translateX(-46%);
|
||||
animation: featurePreviewShimmer 1.9s linear infinite;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featurePreviewLoadingShimmer {
|
||||
background: linear-gradient(115deg, transparent 28%, rgba(143, 194, 255, 0.22) 50%, transparent 72%);
|
||||
}
|
||||
|
||||
@keyframes featurePreviewImageFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes featurePreviewSpinner {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes featurePreviewShimmer {
|
||||
from {
|
||||
transform: translateX(-46%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(46%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Journey */
|
||||
|
||||
+209
-62
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
@@ -19,9 +19,9 @@ type FeatureTab = {
|
||||
darkImage: string;
|
||||
};
|
||||
|
||||
type SummaryCard = {
|
||||
title: string;
|
||||
description: string;
|
||||
type FeatureTabWithUrls = FeatureTab & {
|
||||
lightImageUrl: string;
|
||||
darkImageUrl: string;
|
||||
};
|
||||
|
||||
type DownloadPlatformKey =
|
||||
@@ -72,6 +72,90 @@ const downloadPlatforms: DownloadPlatform[] = [
|
||||
|
||||
const featureAutoplayStoppedKey = 'nyaterm-home-feature-tabs-autoplay-stopped';
|
||||
const featureAutoplayIntervalMs = 4200;
|
||||
const featurePreviewImageCache = new Map<string, 'loaded' | Promise<boolean>>();
|
||||
|
||||
type ThemeMode = 'light' | 'dark';
|
||||
|
||||
function resolveAssetUrl(baseUrl: string, assetPath: string) {
|
||||
if (/^(?:[a-z]+:)?\/\//i.test(assetPath)) {
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
||||
const normalizedPath = assetPath.startsWith('/') ? assetPath.slice(1) : assetPath;
|
||||
return `${normalizedBase}${normalizedPath}`;
|
||||
}
|
||||
|
||||
function isFeaturePreviewImageLoaded(url: string) {
|
||||
return featurePreviewImageCache.get(url) === 'loaded';
|
||||
}
|
||||
|
||||
function preloadFeaturePreviewImage(url: string): Promise<boolean> {
|
||||
if (!url) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const cached = featurePreviewImageCache.get(url);
|
||||
if (cached === 'loaded') {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (typeof Image === 'undefined') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const pending = new Promise<boolean>((resolve) => {
|
||||
const image = new Image();
|
||||
|
||||
image.onload = () => {
|
||||
featurePreviewImageCache.set(url, 'loaded');
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
image.onerror = () => {
|
||||
featurePreviewImageCache.delete(url);
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
image.src = url;
|
||||
});
|
||||
|
||||
featurePreviewImageCache.set(url, pending);
|
||||
return pending;
|
||||
}
|
||||
|
||||
function getThemeModeFromDom(): ThemeMode {
|
||||
if (typeof document === 'undefined') {
|
||||
return 'light';
|
||||
}
|
||||
|
||||
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function useThemeMode() {
|
||||
const [themeMode, setThemeMode] = useState<ThemeMode>(() => getThemeModeFromDom());
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
const syncThemeMode = () => setThemeMode(getThemeModeFromDom());
|
||||
syncThemeMode();
|
||||
|
||||
const observer = new MutationObserver(syncThemeMode);
|
||||
observer.observe(root, {attributes: true, attributeFilter: ['data-theme']});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return themeMode;
|
||||
}
|
||||
|
||||
function detectDownloadPlatform(): DownloadPlatform {
|
||||
if (typeof navigator === 'undefined') {
|
||||
@@ -168,7 +252,7 @@ function DownloadButton() {
|
||||
|
||||
entropyPromise
|
||||
?.then((values) => {
|
||||
const detectedOs = values.platform?.toLowerCase() ?? userAgentData.platform?.toLowerCase() ?? '';
|
||||
const detectedOs = values.platform?.toLowerCase() ?? userAgentData?.platform?.toLowerCase() ?? '';
|
||||
const architecture = values.architecture?.toLowerCase() ?? '';
|
||||
|
||||
if (detectedOs) {
|
||||
@@ -229,31 +313,83 @@ function DownloadButton() {
|
||||
|
||||
function FeaturePreview({
|
||||
title,
|
||||
lightImage,
|
||||
darkImage,
|
||||
lightImageUrl,
|
||||
darkImageUrl,
|
||||
}: {
|
||||
title: string;
|
||||
lightImage: string;
|
||||
darkImage: string;
|
||||
lightImageUrl: string;
|
||||
darkImageUrl: string;
|
||||
}) {
|
||||
const lightImageUrl = useBaseUrl(lightImage);
|
||||
const darkImageUrl = useBaseUrl(darkImage);
|
||||
const themeMode = useThemeMode();
|
||||
const targetImageUrl = themeMode === 'dark' ? darkImageUrl : lightImageUrl;
|
||||
const [displayedImageUrl, setDisplayedImageUrl] = useState(() =>
|
||||
isFeaturePreviewImageLoaded(targetImageUrl) ? targetImageUrl : '',
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(() => !isFeaturePreviewImageLoaded(targetImageUrl));
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
if (!targetImageUrl) {
|
||||
setDisplayedImageUrl('');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFeaturePreviewImageLoaded(targetImageUrl)) {
|
||||
setDisplayedImageUrl(targetImageUrl);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
void preloadFeaturePreviewImage(targetImageUrl).then((loaded) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loaded) {
|
||||
setDisplayedImageUrl(targetImageUrl);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [targetImageUrl]);
|
||||
|
||||
return (
|
||||
<div className={styles.featurePreview} aria-label={title}>
|
||||
<div className={clsx(styles.featurePreviewImageSlot, styles.featurePreviewLight)}>
|
||||
<img
|
||||
className={styles.featurePreviewImage}
|
||||
src={lightImageUrl}
|
||||
alt={translate({message: 'NyaTerm 日间主题功能截图'})}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx(styles.featurePreviewImageSlot, styles.featurePreviewDark)}>
|
||||
<img
|
||||
className={styles.featurePreviewImage}
|
||||
src={darkImageUrl}
|
||||
alt={translate({message: 'NyaTerm 夜间主题功能截图'})}
|
||||
/>
|
||||
<div className={styles.featurePreview} aria-label={title} aria-busy={isLoading}>
|
||||
<div className={clsx(styles.featurePreviewStage, isLoading && styles.featurePreviewStageLoading)}>
|
||||
{displayedImageUrl ? (
|
||||
<img
|
||||
key={displayedImageUrl}
|
||||
className={styles.featurePreviewImage}
|
||||
src={displayedImageUrl}
|
||||
alt={
|
||||
themeMode === 'dark'
|
||||
? translate({message: 'NyaTerm 夜间主题功能截图'})
|
||||
: translate({message: 'NyaTerm 日间主题功能截图'})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.featurePreviewPlaceholder} aria-hidden="true" />
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className={styles.featurePreviewLoadingOverlay} aria-hidden="true">
|
||||
<div className={styles.featurePreviewLoadingShimmer} />
|
||||
<div className={styles.featurePreviewLoadingGlass}>
|
||||
<span className={styles.featurePreviewSpinner} />
|
||||
<span className={styles.featurePreviewLoadingText}>
|
||||
<Translate>加载预览中</Translate>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -352,15 +488,25 @@ function HomepageHeader() {
|
||||
}
|
||||
|
||||
function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
const baseUrl = useBaseUrl('/');
|
||||
const resolvedFeatures = useMemo<FeatureTabWithUrls[]>(
|
||||
() =>
|
||||
features.map((feature) => ({
|
||||
...feature,
|
||||
lightImageUrl: resolveAssetUrl(baseUrl, feature.lightImage),
|
||||
darkImageUrl: resolveAssetUrl(baseUrl, feature.darkImage),
|
||||
})),
|
||||
[baseUrl, features],
|
||||
);
|
||||
const [activeValue, setActiveValue] = useState(features[0]?.value ?? '');
|
||||
const [autoplayStopped, setAutoplayStopped] = useState(false);
|
||||
|
||||
const activeIndex = Math.max(
|
||||
0,
|
||||
features.findIndex((feature) => feature.value === activeValue),
|
||||
resolvedFeatures.findIndex((feature) => feature.value === activeValue),
|
||||
);
|
||||
const activeFeature = features[activeIndex] ?? features[0];
|
||||
const isAutoplaying = !autoplayStopped && features.length > 1;
|
||||
const activeFeature = resolvedFeatures[activeIndex] ?? resolvedFeatures[0];
|
||||
const isAutoplaying = !autoplayStopped && resolvedFeatures.length > 1;
|
||||
|
||||
const stopAutoplay = useCallback(() => {
|
||||
setAutoplayStopped(true);
|
||||
@@ -380,14 +526,14 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
|
||||
const selectFeatureByOffset = useCallback(
|
||||
(offset: number) => {
|
||||
const nextIndex = (activeIndex + offset + features.length) % features.length;
|
||||
const nextValue = features[nextIndex]?.value;
|
||||
const nextIndex = (activeIndex + offset + resolvedFeatures.length) % resolvedFeatures.length;
|
||||
const nextValue = resolvedFeatures[nextIndex]?.value;
|
||||
|
||||
if (nextValue) {
|
||||
selectFeature(nextValue);
|
||||
}
|
||||
},
|
||||
[activeIndex, features, selectFeature],
|
||||
[activeIndex, resolvedFeatures, selectFeature],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -399,10 +545,17 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!features.some((feature) => feature.value === activeValue)) {
|
||||
setActiveValue(features[0]?.value ?? '');
|
||||
if (!resolvedFeatures.some((feature) => feature.value === activeValue)) {
|
||||
setActiveValue(resolvedFeatures[0]?.value ?? '');
|
||||
}
|
||||
}, [activeValue, features]);
|
||||
}, [activeValue, resolvedFeatures]);
|
||||
|
||||
useEffect(() => {
|
||||
resolvedFeatures.forEach((feature) => {
|
||||
void preloadFeaturePreviewImage(feature.lightImageUrl);
|
||||
void preloadFeaturePreviewImage(feature.darkImageUrl);
|
||||
});
|
||||
}, [resolvedFeatures]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoplayStopped) {
|
||||
@@ -438,14 +591,14 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
setActiveValue((currentValue) => {
|
||||
const currentIndex = features.findIndex((feature) => feature.value === currentValue);
|
||||
const nextIndex = currentIndex >= 0 ? (currentIndex + 1) % features.length : 0;
|
||||
return features[nextIndex]?.value ?? currentValue;
|
||||
const currentIndex = resolvedFeatures.findIndex((feature) => feature.value === currentValue);
|
||||
const nextIndex = currentIndex >= 0 ? (currentIndex + 1) % resolvedFeatures.length : 0;
|
||||
return resolvedFeatures[nextIndex]?.value ?? currentValue;
|
||||
});
|
||||
}, featureAutoplayIntervalMs);
|
||||
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [features, isAutoplaying]);
|
||||
}, [isAutoplaying, resolvedFeatures]);
|
||||
|
||||
if (!activeFeature) {
|
||||
return null;
|
||||
@@ -456,7 +609,7 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
<div className="container">
|
||||
<div className={styles.featureShowcase}>
|
||||
<div className={styles.featureTabs} role="tablist" aria-label={translate({message: '功能展示'})}>
|
||||
{features.map((feature, index) => {
|
||||
{resolvedFeatures.map((feature, index) => {
|
||||
const isActive = feature.value === activeFeature.value;
|
||||
|
||||
return (
|
||||
@@ -482,12 +635,12 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
|
||||
if (event.key === 'Home') {
|
||||
event.preventDefault();
|
||||
selectFeature(features[0]?.value ?? feature.value);
|
||||
selectFeature(resolvedFeatures[0]?.value ?? feature.value);
|
||||
}
|
||||
|
||||
if (event.key === 'End') {
|
||||
event.preventDefault();
|
||||
selectFeature(features[features.length - 1]?.value ?? feature.value);
|
||||
selectFeature(resolvedFeatures[resolvedFeatures.length - 1]?.value ?? feature.value);
|
||||
}
|
||||
}}>
|
||||
<span className={styles.featureTabIndex}>{String(index + 1).padStart(2, '0')}</span>
|
||||
@@ -523,8 +676,8 @@ function FeaturesSection({features}: {features: FeatureTab[]}) {
|
||||
|
||||
<FeaturePreview
|
||||
title={activeFeature.title}
|
||||
lightImage={activeFeature.lightImage}
|
||||
darkImage={activeFeature.darkImage}
|
||||
lightImageUrl={activeFeature.lightImageUrl}
|
||||
darkImageUrl={activeFeature.darkImageUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -583,25 +736,6 @@ function JourneySection() {
|
||||
}
|
||||
|
||||
export default function Home(): React.ReactElement {
|
||||
const overviewCards: SummaryCard[] = [
|
||||
{
|
||||
title: translate({message: '多种终端会话'}),
|
||||
description: translate({message: '支持 SSH、本地 shell、Telnet 和串口会话,可在标签页和分屏中同时工作。'}),
|
||||
},
|
||||
{
|
||||
title: translate({message: '终端输出阅读'}),
|
||||
description: translate({message: '提供搜索、命令历史建议、时间戳、关键词高亮和大输出保护。'}),
|
||||
},
|
||||
{
|
||||
title: translate({message: '文件、认证与网络'}),
|
||||
description: translate({message: '包含 SFTP、OTP、代理、跳板机、端口转发和主机密钥校验。'}),
|
||||
},
|
||||
{
|
||||
title: translate({message: '配置同步与备份'}),
|
||||
description: translate({message: '可通过 WebDAV 或 S3 兼容存储保存加密配置快照。'}),
|
||||
},
|
||||
];
|
||||
|
||||
const featureTabs: FeatureTab[] = [
|
||||
{
|
||||
value: 'workspace',
|
||||
@@ -616,6 +750,19 @@ export default function Home(): React.ReactElement {
|
||||
lightImage: '/img/home/overview-light.png',
|
||||
darkImage: '/img/home/overview-dark.png',
|
||||
},
|
||||
{
|
||||
value: 'appearance',
|
||||
label: translate({message: '外观'}),
|
||||
title: translate({message: '用背景图定制主窗口氛围'}),
|
||||
description: translate({message: '主窗口支持本地背景图,可在保留面板可读性的同时营造更个性化的工作区视觉效果。'}),
|
||||
bullets: [
|
||||
translate({message: '使用本地图片作为主窗口的 Background Image'}),
|
||||
translate({message: '通过 cover、contain、stretch 和 tile 控制图片铺法'}),
|
||||
translate({message: '分别调整 Image Opacity 和 Background Content Opacity'}),
|
||||
],
|
||||
lightImage: '/img/home/cover-light.png',
|
||||
darkImage: '/img/home/cover-dark.png',
|
||||
},
|
||||
{
|
||||
value: 'terminal',
|
||||
label: translate({message: '终端'}),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 728 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 929 KiB |
Reference in new issue
Block a user