texlite 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -100
- package/README.zh-CN.md +17 -42
- package/dist/client/assets/{GitDialog-BHHxSBn3.js → GitDialog-LVM9oZ6q.js} +1 -1
- package/dist/client/assets/HistoryDialog-BCpa2ath.js +1 -0
- package/dist/client/assets/{LatexEditor-B-0VRUPv.js → LatexEditor-CEQUB9XV.js} +22 -22
- package/dist/client/assets/{PdfPreview-617TWFJN.js → PdfPreview-URQb_Pk6.js} +1 -1
- package/dist/client/assets/{ProjectNavigationDialogs-Cpt2LVEt.js → ProjectNavigationDialogs-Bf4JwsRy.js} +1 -1
- package/dist/client/assets/{SystemMetricsDialog-fkFHVVzJ.js → SystemMetricsDialog-DwnQFYm7.js} +1 -1
- package/dist/client/assets/index-CtVAEMSo.css +1 -0
- package/dist/client/assets/index-DbzKrh7H.js +17 -0
- package/dist/client/assets/index-rBWMBTI-.js +5 -0
- package/dist/client/assets/{plus-DzbLkp6l.js → plus-D_Y3JlVj.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/server/app.js +35 -1
- package/dist/server/cli.js +95 -8
- package/dist/server/index.js +3 -1
- package/dist/server/latexFormatter.js +116 -0
- package/dist/server/pm2.js +120 -17
- package/dist/server/runtimePaths.js +1 -1
- package/dist/server/service.js +8 -0
- package/package.json +1 -1
- package/dist/client/assets/HistoryDialog-Dlv57tYf.js +0 -5
- package/dist/client/assets/index-Bc5CmFMl.js +0 -15
- package/dist/client/assets/index-CMqYAbDx.css +0 -1
package/README.md
CHANGED
|
@@ -2,53 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
texLite is a lightweight, local-first web workspace for writing, compiling, previewing, and discussing LaTeX documents. It is intended for a small group of trusted users on one server. It uses the LaTeX installation already available on the host instead of shipping a LaTeX container, and keeps the rest of the stack deliberately small.
|
|
4
4
|
|
|
5
|
-
**Documentation:** English (this file) · [简体中文](README.zh-CN.md)
|
|
5
|
+
**Documentation:** English (this file) · [Design](DESIGN.md) · [简体中文](README.zh-CN.md)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/SWUFE-DB-Group/TexLite/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/texlite)
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- Avoid Redis, MongoDB, and a reverse proxy for the default localhost deployment.
|
|
16
|
-
- Keep projects and generated artifacts on the local filesystem.
|
|
17
|
-
- Use SQLite through better-sqlite3 for durable application data.
|
|
18
|
-
- Provide useful collaboration for a small number of active sessions without requiring a distributed deployment.
|
|
19
|
-
|
|
20
|
-
## Features
|
|
21
|
-
|
|
22
|
-
- Administrator initialization and user administration; public registration is disabled.
|
|
23
|
-
- Per-user permission to create projects (disabled by default for newly created users).
|
|
24
|
-
- Project, folder, file, and attachment management, including drag-and-drop uploads.
|
|
25
|
-
- ZIP project import with safe extraction and automatic main-document selection.
|
|
26
|
-
- List/grid project views, search, sorting by last modification or creation time, and private Finder-style color tags.
|
|
27
|
-
- Project rename, source ZIP download, deletion, owner and modification metadata.
|
|
28
|
-
- English and Chinese UI with browser-language detection and a language switcher.
|
|
29
|
-
- CodeMirror LaTeX editing with syntax highlighting, folding, completion, quick file opening, current-file and project-wide search/replace, a cross-file outline, and configurable appearance.
|
|
30
|
-
- Yjs CRDT collaboration with active-session avatars, remote cursors, acknowledged server saves, and a browser-local IndexedDB draft for reconnect recovery; the practical limit is about ten concurrent sessions per project.
|
|
31
|
-
- Automatic content-addressed project history with labels, file comparison, single-file restore, and full-project restore checkpoints.
|
|
32
|
-
- Source-range comments with replies, resolved state, user/time metadata, and anchor remapping after edits.
|
|
33
|
-
- Host-side latexmk compilation with pdflatex, xelatex, and lualatex options, project-level latexmkrc, serialized project jobs, persistent incremental caches, immutable published snapshots, retained PDFs, SyncTeX navigation, structured clickable diagnostics, and downloadable artifacts such as .bbl files.
|
|
34
|
-
- Project sharing with read-only or read/write access. Read-only users can still add and reply to comments.
|
|
35
|
-
- Owner-only local Git history and GitHub backup: commit, push, diff, checkout, and restore tracked changes.
|
|
36
|
-
- Optional PM2 process management for automatic restart, status, and logs.
|
|
37
|
-
- Administrator-only live system metrics for compile queues, collaboration sessions, event-loop delay, memory, and recent operation latency.
|
|
38
|
-
|
|
39
|
-
## Architecture
|
|
40
|
-
|
|
41
|
-
| Area | Implementation |
|
|
42
|
-
| --- | --- |
|
|
43
|
-
| Browser UI | React, Vite, CodeMirror, PDF.js |
|
|
44
|
-
| API and static server | Fastify, WebSocket |
|
|
45
|
-
| Collaboration | Yjs, y-websocket, y-codemirror.next |
|
|
46
|
-
| Database | SQLite via better-sqlite3 |
|
|
47
|
-
| Files | Local project directories under the configured data directory |
|
|
48
|
-
| Compilation | Host latexmk and a configured LaTeX engine |
|
|
49
|
-
| Git backup | Host git and the GitHub REST API |
|
|
50
|
-
|
|
51
|
-
texLite is designed as a single Node.js process. Do not run multiple application instances against the same SQLite database or project directory; the in-memory collaboration service and compile queue are intentionally single-instance.
|
|
12
|
+
For design goals, architecture, collaboration and compilation behavior, history,
|
|
13
|
+
navigation and diagnostics, GitHub backup, and data-management decisions, see
|
|
14
|
+
[DESIGN.md](DESIGN.md).
|
|
52
15
|
|
|
53
16
|
## Requirements
|
|
54
17
|
|
|
@@ -57,6 +20,7 @@ texLite is designed as a single Node.js process. Do not run multiple application
|
|
|
57
20
|
- git (optional; required only for the project-owner Git/GitHub integration)
|
|
58
21
|
- latexmk
|
|
59
22
|
- At least one configured engine: pdflatex, xelatex, or lualatex
|
|
23
|
+
- `tex-fmt` (optional; required only when using LaTeX formatting)
|
|
60
24
|
|
|
61
25
|
Check the host before initialization:
|
|
62
26
|
|
|
@@ -65,12 +29,17 @@ node --version
|
|
|
65
29
|
npm --version
|
|
66
30
|
latexmk --version
|
|
67
31
|
xelatex --version
|
|
32
|
+
# Optional, only when using LaTeX formatting:
|
|
33
|
+
tex-fmt --version
|
|
68
34
|
# Optional, when Git/GitHub integration is needed:
|
|
69
35
|
git --version
|
|
70
36
|
~~~
|
|
71
37
|
|
|
72
38
|
npm run init and application startup check latexmk and every engine in latex.allowedEngines. Git is deliberately excluded from this core check, so a host without Git can initialize and run TexLite normally. When a project owner opens the Git panel or invokes a Git/GitHub operation, TexLite checks git.binary on demand and shows an actionable error if Git is unavailable.
|
|
73
39
|
|
|
40
|
+
Formatting is also optional. If `tex-fmt` is not on the server's `PATH`, the formatting control explains how to install it; TexLite does not silently substitute another formatter. The supported source types are `.tex`, `.bib`, `.cls`, and `.sty`; project-level `tex-fmt.toml` settings are honored.
|
|
41
|
+
If PM2 uses a restricted `PATH`, set `TEXLITE_TEX_FMT` to the absolute path of the host executable.
|
|
42
|
+
|
|
74
43
|
## Quick start
|
|
75
44
|
|
|
76
45
|
### Global npm installation
|
|
@@ -103,7 +72,12 @@ different configuration file.
|
|
|
103
72
|
|
|
104
73
|
`texlite serve` runs the server in the foreground and is suitable for Docker,
|
|
105
74
|
systemd, and debugging. `start`, `status`, `stop`, `restart`, and `logs` use the
|
|
106
|
-
PM2 dependency bundled with the package.
|
|
75
|
+
PM2 dependency bundled with the package. Managed startup waits until the HTTP
|
|
76
|
+
health endpoint is ready; `status` reports `unhealthy` if PM2 is running but the
|
|
77
|
+
managed process is not actually serving TexLite. `restart` recreates the PM2
|
|
78
|
+
entry so npm upgrades always use the current package paths and environment.
|
|
79
|
+
Failed startup attempts are bounded instead of entering an unlimited restart
|
|
80
|
+
loop.
|
|
107
81
|
|
|
108
82
|
For a clean upgrade:
|
|
109
83
|
|
|
@@ -280,6 +254,7 @@ TEXLITE_HISTORY_MAX_VERSIONS TEXLITE_HISTORY_MAX_STORAGE_MB
|
|
|
280
254
|
TEXLITE_LATEXMK TEXLITE_DEFAULT_ENGINE
|
|
281
255
|
TEXLITE_COMPILE_TIMEOUT TEXLITE_MAX_COMPILE_JOBS
|
|
282
256
|
TEXLITE_GIT TEXLITE_GIT_TIMEOUT
|
|
257
|
+
TEXLITE_TEX_FMT
|
|
283
258
|
TEXLITE_GITHUB_API_URL
|
|
284
259
|
~~~
|
|
285
260
|
|
|
@@ -298,6 +273,9 @@ texlite restart
|
|
|
298
273
|
texlite stop
|
|
299
274
|
~~~
|
|
300
275
|
|
|
276
|
+
`texlite status` uses a colored, systemctl-style terminal view. Use
|
|
277
|
+
`texlite status --json` for scripts and monitoring integrations.
|
|
278
|
+
|
|
301
279
|
`texlite doctor` validates configuration, paths, LaTeX, and the administrator;
|
|
302
280
|
add `--git` to check the optional Git integration. `texlite config` prints the
|
|
303
281
|
effective paths and values. `texlite serve` keeps the process in the foreground
|
|
@@ -335,64 +313,6 @@ pm2 save
|
|
|
335
313
|
|
|
336
314
|
Useful lifecycle commands are pm2 stop texlite, pm2 restart texlite, pm2 delete texlite, and pm2 monit.
|
|
337
315
|
|
|
338
|
-
## Collaboration and compilation model
|
|
339
|
-
|
|
340
|
-
The editor uses a Yjs CRDT document for concurrent editing. Each active browser session is shown in the project header, and remote cursor colors identify editors. The intended scale is up to roughly ten active sessions on one project. The save indicator changes to “saved” only after the server acknowledges durable source persistence. Unsynchronized updates are also retained in browser IndexedDB and replayed after a transient disconnect; a history restore or Git checkout rotates the collaboration epoch so an obsolete local draft cannot overwrite the restored tree. Deleting or moving a file rejects late edits from sessions that still held its old editor binding.
|
|
341
|
-
|
|
342
|
-
The main document saved in project settings is the default root. When a user opens another `.tex` file containing `\documentclass`, that file becomes the compile root for that browser session; opening an included fragment does not change the current root. Only the current root is compiled. Compile status, logs, retained PDF, artifacts, outline, and SyncTeX state are keyed by root document, so collaborators working on different roots do not see each other's compile notification or replace each other's PDF.
|
|
343
|
-
|
|
344
|
-
Compilation is isolated from editing:
|
|
345
|
-
|
|
346
|
-
1. The server captures an immutable source snapshot.
|
|
347
|
-
2. Changed files are synchronized into a persistent compile workspace keyed by project, root document, and compiler settings.
|
|
348
|
-
3. latexmk reuses its dependency database and auxiliary files; jobs for the same root remain serialized.
|
|
349
|
-
4. The resulting PDF, logs, SyncTeX, and other artifacts are copied into an immutable run bundle.
|
|
350
|
-
5. A successful bundle is published atomically as the latest retained result.
|
|
351
|
-
|
|
352
|
-
The mutable cache for one root is never used concurrently, while different roots have separate caches and published artifacts are never modified in place. A user can continue viewing the previous PDF while a new compile is running. latexmk handles the necessary repeated passes for BibTeX-based documents and avoids rerunning BibTeX when its inputs have not changed. Compile responses expose a Server-Timing header for snapshot, cache synchronization, latexmk, artifact-copy, and total request time. TexLite does not retain a browsable compile history: it keeps only the latest attempt state and, when needed, the last successful result currently published for each root document. Older database rows and immutable artifact bundles are removed automatically.
|
|
353
|
-
|
|
354
|
-
## History, navigation, and diagnostics
|
|
355
|
-
|
|
356
|
-
Automatic history records source/file operations, compiler-setting changes, Git operations, restores, and acknowledged collaborative saves. Collaborative saves use fixed two-minute version windows, so continuous editing still creates useful recovery points without recording every keystroke. TexLite retains the latest 200 ordinary versions by default, plus every labeled and initial version. File contents are stored as complete SHA-256-addressed objects: unchanged data is reused, while a changed file creates a new complete object. A 512 MB soft per-project limit removes the oldest ordinary versions first; protected versions and the current internal baseline may exceed it. Project owners can see history storage usage and delete one version or clear all history without changing current project files. Unreferenced objects are garbage-collected. History is useful for correcting writing mistakes, but it is not a replacement for backing up the complete data directory.
|
|
357
|
-
|
|
358
|
-
Use Ctrl/Cmd+P to open a project file quickly and Ctrl/Cmd+Shift+F for literal project-wide search and replace. Project replacement is staged as one operation and creates a history version. The outline follows `\\input`, `\\include`, and `\\subfile` references from the main document. Structured warning/error entries resolve project-relative file names and jump directly to the matching source line; the raw latexmk transcript remains available.
|
|
359
|
-
|
|
360
|
-
Administrators can open **System status** on the project list. It reports only counts and timings—never source text, passwords, Git tokens, or comments. The same data is available at authenticated endpoint `GET /api/health/metrics`.
|
|
361
|
-
|
|
362
|
-
## GitHub backup
|
|
363
|
-
|
|
364
|
-
The Git panel is visible only to the project owner. A GitHub personal access token is configured per project and stored encrypted in SQLite. The local repository is initialized in the project source directory; Git identity is temporary for each command:
|
|
365
|
-
|
|
366
|
-
~~~text
|
|
367
|
-
user.name = project owner's username
|
|
368
|
-
user.email = <username>@texlite.com
|
|
369
|
-
~~~
|
|
370
|
-
|
|
371
|
-
For a fine-grained GitHub token, the intended trusted-user deployment should grant repository Administration and Contents read/write permissions and use All repositories, because a repository may not exist when the token is configured and later repositories may be created. The token never appears in the remote URL or command-line arguments.
|
|
372
|
-
|
|
373
|
-
Normal checkout uses Git's preserving behavior and refuses conflicts. Only the explicit force option discards tracked, untracked, and ignored working-tree files. After checking out a historical revision, return to the default branch before committing or pushing.
|
|
374
|
-
|
|
375
|
-
## Data, backup, and deletion
|
|
376
|
-
|
|
377
|
-
The default data directory is:
|
|
378
|
-
|
|
379
|
-
~~~text
|
|
380
|
-
.texlite/
|
|
381
|
-
├── texlite.db
|
|
382
|
-
├── texlite.db-wal
|
|
383
|
-
├── texlite.db-shm
|
|
384
|
-
├── git-token.key
|
|
385
|
-
└── projects/
|
|
386
|
-
└── <project-id>/
|
|
387
|
-
├── source/
|
|
388
|
-
└── output/
|
|
389
|
-
└── .texlite/ # compile cache/runs and history objects
|
|
390
|
-
~~~
|
|
391
|
-
|
|
392
|
-
Back up texlite.db, git-token.key, and projects/ together. The encryption key is required to recover saved GitHub tokens. SQLite WAL files should be included when taking a live filesystem backup, or use a SQLite-aware backup procedure.
|
|
393
|
-
|
|
394
|
-
Deleting a user removes their memberships and comments remain attributable as “Deleted User”. Depending on the administrator's choice, projects owned by that user can be transferred to the current administrator or deleted together with their files. The last active administrator cannot be removed or disabled.
|
|
395
|
-
|
|
396
316
|
## Security boundaries
|
|
397
317
|
|
|
398
318
|
texLite is designed for trusted users on localhost. The default compiler disables shell escape, does not concatenate shell commands, and enforces compile timeouts and concurrency limits. Nevertheless, LaTeX itself and a project latexmkrc are not a security sandbox. Before exposing texLite to an untrusted network or public registration, add an isolated compiler sandbox and an appropriate authentication/reverse-proxy layer.
|
package/README.zh-CN.md
CHANGED
|
@@ -2,53 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
texLite 是一个轻量、以本机为中心的 LaTeX 网页工作区,用于编写、编译、预览和讨论 LaTeX 文档。它面向同一台服务器上的少量可信用户。texLite 使用服务器已经安装的 LaTeX 环境,不打包 LaTeX 镜像;其余技术栈也尽量保持简单。
|
|
4
4
|
|
|
5
|
-
**文档:** [English](README.md) · 简体中文(当前文件)
|
|
5
|
+
**文档:** [English](README.md) · [Design(英文)](DESIGN.md) · 简体中文(当前文件)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/SWUFE-DB-Group/TexLite/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/texlite)
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- 使用宿主机已有的 TeX Live/LaTeX,使 LaTeX 可以独立更新。
|
|
15
|
-
- 默认不依赖 Redis、MongoDB、Caddy 或 Nginx,适合 localhost 部署。
|
|
16
|
-
- 项目源码和编译产物保存在本地文件系统。
|
|
17
|
-
- 使用 better-sqlite3 访问 SQLite,保存持久化应用数据。
|
|
18
|
-
- 在不引入分布式部署的前提下,为少量并发会话提供实用的协作能力。
|
|
19
|
-
|
|
20
|
-
## 功能
|
|
21
|
-
|
|
22
|
-
- 管理员初始化和用户管理;不开放公众注册。
|
|
23
|
-
- 用户独立的“创建项目”权限,新用户默认关闭。
|
|
24
|
-
- 项目、文件夹、文件和资源管理,支持拖拽上传。
|
|
25
|
-
- ZIP 项目导入、安全解压和主文档自动选择。
|
|
26
|
-
- 项目列表/网格视图、搜索、按最后修改时间或创建时间排序,以及每个用户私有的 Finder 风格彩色标签。
|
|
27
|
-
- 项目重命名、源码 ZIP 下载、删除,以及所有者和修改信息展示。
|
|
28
|
-
- 中文和英文界面,自动识别浏览器语言,并可手动切换。
|
|
29
|
-
- 基于 CodeMirror 的 LaTeX 编辑器:语法高亮、折叠、补全、快速打开文件、当前文件及全项目搜索/替换、跨文件大纲和外观设置。
|
|
30
|
-
- 基于 Yjs CRDT 的协作编辑,显示活动会话头像、远程光标,保存状态以服务端持久化确认为准,并用浏览器 IndexedDB 保留断线草稿;单个项目建议不超过约 10 个并发会话。
|
|
31
|
-
- 内容寻址的自动项目历史:可添加版本标签、比较文件、恢复单个文件或带安全检查点地恢复整个项目。
|
|
32
|
-
- 源码范围批注、回复、resolved 状态、用户/时间信息,以及源码变化后的批注锚点重映射。
|
|
33
|
-
- 使用宿主机 latexmk 编译,支持 pdflatex、xelatex、lualatex、项目级 latexmkrc、项目编译串行化、持久增量缓存、不可变发布快照、最新 PDF 保留、SyncTeX 跳转、可点击的结构化诊断和 .bbl 等编译产物下载。
|
|
34
|
-
- 项目共享支持只读和读写权限;只读用户仍然可以添加和回复批注。
|
|
35
|
-
- 项目所有者专用的本地 Git 历史和 GitHub 备份:commit、push、diff、checkout 和恢复已跟踪修改。
|
|
36
|
-
- 可选 PM2 进程管理,支持异常重启、状态查看和日志管理。
|
|
37
|
-
- 仅管理员可查看的系统状态:编译队列、协作会话、事件循环延迟、内存和近期操作耗时。
|
|
38
|
-
|
|
39
|
-
## 技术结构
|
|
40
|
-
|
|
41
|
-
| 部分 | 实现 |
|
|
42
|
-
| --- | --- |
|
|
43
|
-
| 浏览器界面 | React、Vite、CodeMirror、PDF.js |
|
|
44
|
-
| API 和静态服务 | Fastify、WebSocket |
|
|
45
|
-
| 协作 | Yjs、y-websocket、y-codemirror.next |
|
|
46
|
-
| 数据库 | better-sqlite3 访问 SQLite |
|
|
47
|
-
| 文件 | 配置数据目录下的本地项目目录 |
|
|
48
|
-
| 编译 | 宿主机 latexmk 和配置的 LaTeX 引擎 |
|
|
49
|
-
| Git 备份 | 宿主机 git 和 GitHub REST API |
|
|
50
|
-
|
|
51
|
-
texLite 按单个 Node.js 进程设计。不要让多个应用实例同时使用同一个 SQLite 数据库或项目目录;协作状态、编译队列和文件系统都是单进程资源。
|
|
12
|
+
设计目标、技术架构、协作与编译模型、历史与诊断、GitHub 备份以及数据管理
|
|
13
|
+
请参阅英文文档 [DESIGN.md](DESIGN.md)。
|
|
52
14
|
|
|
53
15
|
## 环境要求
|
|
54
16
|
|
|
@@ -57,6 +19,7 @@ texLite 按单个 Node.js 进程设计。不要让多个应用实例同时使用
|
|
|
57
19
|
- git(可选;仅项目所有者使用 Git/GitHub 集成时需要)
|
|
58
20
|
- latexmk
|
|
59
21
|
- 至少一个配置好的引擎:pdflatex、xelatex 或 lualatex
|
|
22
|
+
- `tex-fmt`(可选;仅使用 LaTeX 格式化功能时需要)
|
|
60
23
|
|
|
61
24
|
初始化前可以检查:
|
|
62
25
|
|
|
@@ -65,12 +28,17 @@ node --version
|
|
|
65
28
|
npm --version
|
|
66
29
|
latexmk --version
|
|
67
30
|
xelatex --version
|
|
31
|
+
# 可选,仅在使用 LaTeX 格式化时检查:
|
|
32
|
+
tex-fmt --version
|
|
68
33
|
# 可选,仅在需要 Git/GitHub 集成时检查:
|
|
69
34
|
git --version
|
|
70
35
|
~~~
|
|
71
36
|
|
|
72
37
|
npm run init 和应用启动会检查 latexmk 以及 latex.allowedEngines 中列出的每个引擎。Git 不参与这项核心检查,因此未安装 Git 的宿主机仍可正常初始化并运行 TexLite。项目所有者打开 Git 面板或执行 Git/GitHub 操作时,TexLite 才会按需检查 git.binary;如果 Git 不可用,界面会给出可操作的错误提示。
|
|
73
38
|
|
|
39
|
+
格式化功能同样是可选的。如果服务器的 `PATH` 中没有 `tex-fmt`,格式化控件会提示安装方法;TexLite 不会静默替换成其他格式化器。支持 `.tex`、`.bib`、`.cls` 和 `.sty`,项目中的 `tex-fmt.toml` 配置会自动生效。
|
|
40
|
+
如果 PM2 使用了受限的 `PATH`,可以将 `TEXLITE_TEX_FMT` 设置为宿主机可执行文件的绝对路径。
|
|
41
|
+
|
|
74
42
|
## 快速开始
|
|
75
43
|
|
|
76
44
|
### 全局 npm 安装
|
|
@@ -101,7 +69,10 @@ texlite logs
|
|
|
101
69
|
指定其他配置文件。
|
|
102
70
|
|
|
103
71
|
`texlite serve` 以前台方式运行,适合 Docker、systemd 和调试。
|
|
104
|
-
`start`、`status`、`stop`、`restart` 和 `logs` 使用 npm 包内置的 PM2
|
|
72
|
+
`start`、`status`、`stop`、`restart` 和 `logs` 使用 npm 包内置的 PM2。后台启动会
|
|
73
|
+
等待 HTTP 健康检查真正就绪;如果 PM2 进程存在但没有实际提供 TexLite 服务,
|
|
74
|
+
`status` 会显示 `unhealthy`。`restart` 会重新创建 PM2 条目,确保 npm 升级后使用
|
|
75
|
+
当前版本的包路径和环境变量。启动失败的重试次数是有限的,不会进入无限重启循环。
|
|
105
76
|
|
|
106
77
|
升级时可以执行:
|
|
107
78
|
|
|
@@ -271,6 +242,7 @@ TEXLITE_HISTORY_MAX_VERSIONS TEXLITE_HISTORY_MAX_STORAGE_MB
|
|
|
271
242
|
TEXLITE_LATEXMK TEXLITE_DEFAULT_ENGINE
|
|
272
243
|
TEXLITE_COMPILE_TIMEOUT TEXLITE_MAX_COMPILE_JOBS
|
|
273
244
|
TEXLITE_GIT TEXLITE_GIT_TIMEOUT
|
|
245
|
+
TEXLITE_TEX_FMT
|
|
274
246
|
TEXLITE_GITHUB_API_URL
|
|
275
247
|
~~~
|
|
276
248
|
|
|
@@ -288,6 +260,9 @@ texlite restart
|
|
|
288
260
|
texlite stop
|
|
289
261
|
~~~
|
|
290
262
|
|
|
263
|
+
`texlite status` 默认使用带颜色的 systemctl 风格终端视图;脚本或监控集成可使用
|
|
264
|
+
`texlite status --json` 获取结构化输出。
|
|
265
|
+
|
|
291
266
|
`texlite doctor` 会校验配置、路径、LaTeX 和管理员;使用 `--git` 可以额外
|
|
292
267
|
检查可选的 Git 集成。`texlite config` 显示生效的配置和路径。
|
|
293
268
|
`texlite serve` 以前台方式运行,不启动 PM2。
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{u as ye,r as n,j as e}from"./react-CsO_xvbl.js";import{i as h,M as H,L as p,G as q,C as ie,j as ne,T as ke,R as be,k as l}from"./index-Bc5CmFMl.js";import{M as ve,P as Ce,a as Ne}from"./plus-DzbLkp6l.js";const we=h("CloudUpload",[["path",{d:"M12 13v8",key:"1l5pq0"}],["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242",key:"1pljnt"}],["path",{d:"m8 17 4-4 4 4",key:"1quai1"}]]);const ae=h("ExternalLink",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]);const G=h("Eye",[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);const oe=h("GitCommitHorizontal",[["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}],["line",{x1:"3",x2:"9",y1:"12",y2:"12",key:"1dyftd"}],["line",{x1:"15",x2:"21",y1:"12",y2:"12",key:"oup4p8"}]]);const W=h("Github",[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]]);const ze=h("KeyRound",[["path",{d:"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",key:"1s6t7t"}],["circle",{cx:"16.5",cy:"7.5",r:".5",fill:"currentColor",key:"w0ekpg"}]]);const Se=h("Minimize2",[["polyline",{points:"4 14 10 14 10 20",key:"11kfnr"}],["polyline",{points:"20 10 14 10 14 4",key:"rlmsce"}],["line",{x1:"14",x2:"21",y1:"10",y2:"3",key:"o5lafz"}],["line",{x1:"3",x2:"10",y1:"21",y2:"14",key:"1atl0r"}]]);const Ee=h("RefreshCcw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]);const Te=h("Undo2",[["path",{d:"M9 14 4 9l5-5",key:"102s5s"}],["path",{d:"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11",key:"f3b9sd"}]]);function Ue({open:g,project:o,onOpenChange:y,onBeforeMutation:r}){const{t:s,i18n:E}=ye(),[i,m]=n.useState(null),[J,V]=n.useState([]),[T,k]=n.useState(""),[F,K]=n.useState(()=>re(o.name)),[_,ce]=n.useState(!0),[D,Z]=n.useState(""),[b,M]=n.useState(null),[f,j]=n.useState(null),[R,U]=n.useState(!1),[le,v]=n.useState(!1),[$,L]=n.useState(!0),[P,A]=n.useState(!0),[a,C]=n.useState(""),[Q,N]=n.useState(""),[X,B]=n.useState(""),[u,x]=n.useState(!1),[w,Y]=n.useState(10),O=n.useRef(null),z=async(t=!1)=>{const c=await l(`/api/projects/${o.id}/git`);if(m(c.status),t&&(L(!c.status.tokenConfigured),A(!c.status.remoteUrl)),c.status.initialized&&c.status.latestCommit){const S=await l(`/api/projects/${o.id}/git/history`);V(S.commits)}else V([])};n.useEffect(()=>{if(!g){document.fullscreenElement&&document.exitFullscreen().catch(()=>{}),x(!1),k("");return}N(""),B(""),M(null),x(!1),K(re(o.name)),C("load"),r().then(()=>z(!0)).catch(t=>N(t instanceof Error?t.message:s("errors.generic"))).finally(()=>C(""))},[g,o.id]),n.useEffect(()=>{const t=()=>x(document.fullscreenElement===O.current);return document.addEventListener("fullscreenchange",t),()=>document.removeEventListener("fullscreenchange",t)},[]),n.useEffect(()=>{if(!u)return;const t=c=>{c.key==="Escape"&&!document.fullscreenElement&&x(!1)};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[u]);const d=async(t,c,S,ee)=>{C(t),N(""),B("");try{await c(),S&&B(S)}catch(se){const te=se instanceof Error?se.message:s("errors.generic");N(ee?`${ee}: ${te}`:te)}finally{C("")}},de=()=>d("token",async()=>{const t=await l(`/api/projects/${o.id}/git/token`,{method:"PUT",body:JSON.stringify({token:T})});m(t.status),k(""),L(!1)},s("git.tokenSaved")),he=()=>d("remove-token",async()=>{const t=await l(`/api/projects/${o.id}/git/token`,{method:"DELETE"});m(t.status),k("")},s("git.tokenRemoved")),ge=()=>d("repository",async()=>{const t=await l(`/api/projects/${o.id}/git/repository`,{method:"POST",body:JSON.stringify({name:F,private:_})});m(t.status),A(!1)},s("git.repositoryCreated")),ue=()=>d("commit",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const t=await l(`/api/projects/${o.id}/git/commit`,{method:"POST",body:JSON.stringify({message:D})});m(t.status),Z(""),M(null),await z()},s("git.committed")),me=()=>d("push",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const t=await l(`/api/projects/${o.id}/git/push`,{method:"POST"});m(t.status),await z()},s("git.pushed"),s("git.pushFailed")),I=t=>d("diff",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const c=t?`?revision=${encodeURIComponent(t)}`:"";M(await l(`/api/projects/${o.id}/git/diff${c}`))}),fe=async()=>{if(u){document.fullscreenElement&&await document.exitFullscreen().catch(()=>{}),x(!1);return}const t=O.current;if(t?.requestFullscreen)try{await t.requestFullscreen()}catch{}x(!0)},xe=()=>{const t=f;t&&(j(null),d("checkout",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));await l(`/api/projects/${o.id}/git/checkout`,{method:"POST",body:JSON.stringify({revision:t==="branch"?null:t.sha,force:R})}),window.location.reload()}))},pe=()=>{v(!1),d("discard",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));await l(`/api/projects/${o.id}/git/discard`,{method:"POST"}),window.location.reload()})},je=a==="load"&&!i;return e.jsxs(e.Fragment,{children:[e.jsx(H,{open:g,extraWide:!0,className:u?"git-dialog-modal-fullscreen":void 0,title:s("git.title"),description:s("git.description"),onOpenChange:y,footer:e.jsx("button",{onClick:()=>y(!1),children:s("common.close")}),children:e.jsxs("div",{className:"git-dialog",children:[je&&e.jsxs("div",{className:"git-loading",children:[e.jsx(p,{className:"spin",size:22}),s("common.loading")]}),Q&&e.jsx("p",{className:"git-message error",children:Q}),X&&e.jsxs("p",{className:"git-message success",children:[e.jsx(q,{size:14}),X]}),i&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"git-section",children:[e.jsxs("button",{className:"git-section-toggle",type:"button","aria-expanded":$,onClick:()=>L(t=>!t),children:[e.jsxs("span",{className:"git-section-heading",children:[e.jsx(ze,{size:16}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.authentication")}),e.jsx("small",{children:i.tokenConfigured?s("git.connectedAs",{login:i.githubLogin}):s("git.authenticationHint")})]})]}),$?e.jsx(ie,{size:16}):e.jsx(ne,{size:16})]}),$&&e.jsxs("div",{className:"git-section-content",children:[e.jsxs("div",{className:"git-token-guidance",children:[e.jsx("strong",{children:s("git.recommendedAccessTitle")}),e.jsx("span",{children:s("git.recommendedAccess")})]}),i.tokenConfigured&&e.jsxs("div",{className:"git-account",children:[e.jsx(W,{size:17}),e.jsx("span",{children:s("git.connectedAs",{login:i.githubLogin})}),e.jsxs("button",{className:"danger-text",disabled:!!a,onClick:he,children:[e.jsx(ke,{size:14}),s("git.removeToken")]})]}),e.jsxs("label",{className:"form-field",children:[i.tokenConfigured?s("git.replaceToken"):s("git.token"),e.jsx("input",{type:"password",autoComplete:"new-password",spellCheck:!1,autoCapitalize:"none",autoCorrect:"off",value:T,placeholder:s("git.tokenPlaceholder"),onChange:t=>k(t.target.value)})]}),e.jsxs("div",{className:"git-inline-actions",children:[e.jsxs("a",{href:"https://github.com/settings/personal-access-tokens/new",target:"_blank",rel:"noreferrer",children:[s("git.createToken"),e.jsx(ae,{size:12})]}),e.jsxs("button",{disabled:!!a||T.trim().length<20,onClick:de,children:[a==="token"&&e.jsx(p,{className:"spin",size:13}),s("git.saveToken")]})]})]})]}),e.jsxs("section",{className:"git-section",children:[e.jsxs("button",{className:"git-section-toggle",type:"button","aria-expanded":P,onClick:()=>A(t=>!t),children:[e.jsxs("span",{className:"git-section-heading",children:[e.jsx(W,{size:16}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.repository")}),e.jsx("small",{children:i.remoteUrl?i.repositoryName??i.remoteUrl:i.initialized?s("git.localReady"):s("git.localPending")})]})]}),P?e.jsx(ie,{size:16}):e.jsx(ne,{size:16})]}),P&&e.jsx("div",{className:"git-section-content",children:i.remoteUrl?e.jsxs("div",{className:"git-repository-card",children:[e.jsxs("div",{children:[e.jsx("strong",{children:i.repositoryName??s("git.origin")}),e.jsxs("small",{children:[i.branch??s("git.detachedAt",{revision:i.latestCommit?.shortSha??"HEAD"})," · ",i.dirty?s("git.changedFiles",{count:i.changedFiles}):s("git.clean")]})]}),i.repositoryHtmlUrl&&e.jsxs("a",{href:i.repositoryHtmlUrl,target:"_blank",rel:"noreferrer",children:[e.jsx(ae,{size:14}),s("git.openGitHub")]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("label",{className:"form-field",children:[s("git.repositoryName"),e.jsx("input",{value:F,onChange:t=>K(t.target.value)})]}),e.jsxs("label",{className:"checkbox-field",children:[e.jsx("input",{type:"checkbox",checked:_,onChange:t=>ce(t.target.checked)}),s("git.privateRepository")]}),e.jsxs("button",{className:"git-primary-action",disabled:!!a||!i.tokenConfigured||!F.trim(),onClick:ge,children:[a==="repository"?e.jsx(p,{className:"spin",size:14}):e.jsx(W,{size:14}),s("git.createRepository")]})]})})]}),i.initialized&&e.jsxs("section",{className:"git-section",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(oe,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.commitAndPush")}),e.jsx("small",{children:i.branch?s("git.identity",{username:o.ownerUsername}):s("git.detachedHead")})]})]}),e.jsxs("label",{className:"form-field",children:[s("git.commitMessage"),e.jsx("textarea",{rows:3,value:D,placeholder:s("git.commitPlaceholder"),onChange:t=>Z(t.target.value)})]}),e.jsxs("div",{className:"git-action-row",children:[e.jsxs("button",{title:i.dirty?void 0:s("git.noChangesToCommit"),disabled:!!a||!i.branch||!i.dirty||!D.trim(),onClick:ue,children:[a==="commit"?e.jsx(p,{className:"spin",size:14}):e.jsx(oe,{size:14}),s("git.commit")]}),e.jsxs("button",{disabled:!!a||!i.branch||!i.tokenConfigured||!i.remoteUrl||!i.latestCommit,onClick:me,children:[a==="push"?e.jsx(p,{className:"spin",size:14}):e.jsx(we,{size:14}),s("git.push"),i.ahead>0&&e.jsx("span",{className:"git-count",children:i.ahead})]}),e.jsxs("button",{disabled:!!a||!i.latestCommit,onClick:()=>{I()},children:[a==="diff"?e.jsx(p,{className:"spin",size:14}):e.jsx(G,{size:14}),s("git.workingDiff")]}),e.jsxs("button",{className:"git-discard-button",title:i.restorable?s("git.discardChanges"):s("git.noChangesToDiscard"),disabled:!!a||!i.latestCommit||!i.restorable,onClick:()=>v(!0),children:[e.jsx(be,{size:14}),s("git.discardChanges")]}),!i.branch&&e.jsxs("button",{disabled:!!a,onClick:()=>{U(!1),j("branch")},children:[e.jsx(q,{size:14}),s("git.returnToBranch",{branch:i.defaultBranch})]}),e.jsxs("button",{disabled:!!a,onClick:()=>{d("refresh",z)},children:[e.jsx(Ee,{className:a==="refresh"?"spin":"",size:14}),s("git.refresh")]})]})]}),b&&e.jsxs("section",{ref:O,className:`git-section git-diff-section${u?" is-fullscreen":""}`,children:[e.jsxs("div",{className:"git-diff-heading",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(G,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.diffTitle")}),e.jsxs("small",{children:[b.title,b.truncated?` · ${s("git.diffTruncated")}`:""]})]})]}),e.jsxs("div",{className:"git-diff-controls",children:[e.jsxs("div",{className:"git-diff-font-controls",role:"group","aria-label":s("git.diffFontSize"),children:[e.jsx("button",{type:"button",className:"git-diff-font-button",disabled:w<=8,title:s("git.diffFontDecrease"),"aria-label":s("git.diffFontDecrease"),onClick:()=>Y(t=>Math.max(8,t-1)),children:e.jsx(ve,{size:14})}),e.jsxs("span",{className:"git-diff-font-value","aria-live":"polite",children:[w,"px"]}),e.jsx("button",{type:"button",className:"git-diff-font-button",disabled:w>=24,title:s("git.diffFontIncrease"),"aria-label":s("git.diffFontIncrease"),onClick:()=>Y(t=>Math.min(24,t+1)),children:e.jsx(Ce,{size:14})})]}),e.jsx("button",{type:"button",className:"git-diff-fullscreen",title:s(u?"git.exitFullscreenDiff":"git.fullscreenDiff"),"aria-label":s(u?"git.exitFullscreenDiff":"git.fullscreenDiff"),onClick:()=>{fe()},children:u?e.jsx(Se,{size:15}):e.jsx(Ne,{size:15})})]})]}),e.jsx(Fe,{content:b.diff,empty:s("git.noChanges"),fontSize:w})]}),i.initialized&&e.jsxs("section",{className:"git-section",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(q,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.history")}),e.jsx("small",{children:s("git.historyHint")})]})]}),e.jsxs("div",{className:"git-history",children:[J.map(t=>e.jsxs("article",{children:[e.jsx("code",{children:t.shortSha}),e.jsxs("div",{children:[e.jsx("strong",{children:t.message}),e.jsxs("small",{children:[t.authorName," · ",new Date(t.authoredAt).toLocaleString(E.resolvedLanguage)]})]}),e.jsxs("span",{children:[e.jsx("button",{title:s("git.viewCommitDiff"),disabled:!!a,onClick:()=>{I(t.sha)},children:e.jsx(G,{size:14})}),e.jsx("button",{title:s("git.checkoutVersion"),disabled:!!a,onClick:()=>{U(!1),j(t)},children:e.jsx(Te,{size:14})})]})]},t.sha)),J.length===0&&e.jsx("p",{className:"muted",children:s("git.noCommits")})]})]})]})]})}),e.jsx(H,{open:!!f,title:s(f==="branch"?"git.returnTitle":"git.checkoutTitle"),description:f==="branch"?s("git.returnDescription",{branch:i?.defaultBranch??"main"}):s("git.checkoutDescription",{revision:f?.shortSha??"",message:f?.message??""}),onOpenChange:t=>{t||j(null)},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>j(null),children:s("common.cancel")}),e.jsx("button",{className:R?"danger":"primary",onClick:xe,children:s("git.checkout")})]}),children:e.jsxs("label",{className:"git-force-checkout",children:[e.jsx("input",{type:"checkbox",checked:R,onChange:t=>U(t.target.checked)}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.forceCheckout")}),e.jsx("small",{children:s("git.forceCheckoutDescription")})]})]})}),e.jsx(H,{open:le,title:s("git.discardTitle"),description:s("git.discardDescription"),onOpenChange:v,footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>v(!1),children:s("common.cancel")}),e.jsx("button",{className:"danger",onClick:pe,children:s("git.discardConfirm")})]}),children:e.jsx(e.Fragment,{})})]})}function Fe({content:g,empty:o,fontSize:y}){return g.trim()?e.jsx("pre",{className:"git-diff",style:{fontSize:`${y}px`},children:g.split(`
|
|
1
|
+
import{u as ye,r as n,j as e}from"./react-CsO_xvbl.js";import{i as h,M as H,L as p,G as q,C as ie,j as ne,T as ke,R as be,k as l}from"./index-DbzKrh7H.js";import{M as ve,P as Ce,a as Ne}from"./plus-D_Y3JlVj.js";const we=h("CloudUpload",[["path",{d:"M12 13v8",key:"1l5pq0"}],["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242",key:"1pljnt"}],["path",{d:"m8 17 4-4 4 4",key:"1quai1"}]]);const ae=h("ExternalLink",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]);const G=h("Eye",[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);const oe=h("GitCommitHorizontal",[["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}],["line",{x1:"3",x2:"9",y1:"12",y2:"12",key:"1dyftd"}],["line",{x1:"15",x2:"21",y1:"12",y2:"12",key:"oup4p8"}]]);const W=h("Github",[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]]);const ze=h("KeyRound",[["path",{d:"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",key:"1s6t7t"}],["circle",{cx:"16.5",cy:"7.5",r:".5",fill:"currentColor",key:"w0ekpg"}]]);const Se=h("Minimize2",[["polyline",{points:"4 14 10 14 10 20",key:"11kfnr"}],["polyline",{points:"20 10 14 10 14 4",key:"rlmsce"}],["line",{x1:"14",x2:"21",y1:"10",y2:"3",key:"o5lafz"}],["line",{x1:"3",x2:"10",y1:"21",y2:"14",key:"1atl0r"}]]);const Ee=h("RefreshCcw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]);const Te=h("Undo2",[["path",{d:"M9 14 4 9l5-5",key:"102s5s"}],["path",{d:"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11",key:"f3b9sd"}]]);function Ue({open:g,project:o,onOpenChange:y,onBeforeMutation:r}){const{t:s,i18n:E}=ye(),[i,m]=n.useState(null),[J,V]=n.useState([]),[T,k]=n.useState(""),[F,K]=n.useState(()=>re(o.name)),[_,ce]=n.useState(!0),[D,Z]=n.useState(""),[b,M]=n.useState(null),[f,j]=n.useState(null),[R,U]=n.useState(!1),[le,v]=n.useState(!1),[$,L]=n.useState(!0),[P,A]=n.useState(!0),[a,C]=n.useState(""),[Q,N]=n.useState(""),[X,B]=n.useState(""),[u,x]=n.useState(!1),[w,Y]=n.useState(10),O=n.useRef(null),z=async(t=!1)=>{const c=await l(`/api/projects/${o.id}/git`);if(m(c.status),t&&(L(!c.status.tokenConfigured),A(!c.status.remoteUrl)),c.status.initialized&&c.status.latestCommit){const S=await l(`/api/projects/${o.id}/git/history`);V(S.commits)}else V([])};n.useEffect(()=>{if(!g){document.fullscreenElement&&document.exitFullscreen().catch(()=>{}),x(!1),k("");return}N(""),B(""),M(null),x(!1),K(re(o.name)),C("load"),r().then(()=>z(!0)).catch(t=>N(t instanceof Error?t.message:s("errors.generic"))).finally(()=>C(""))},[g,o.id]),n.useEffect(()=>{const t=()=>x(document.fullscreenElement===O.current);return document.addEventListener("fullscreenchange",t),()=>document.removeEventListener("fullscreenchange",t)},[]),n.useEffect(()=>{if(!u)return;const t=c=>{c.key==="Escape"&&!document.fullscreenElement&&x(!1)};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[u]);const d=async(t,c,S,ee)=>{C(t),N(""),B("");try{await c(),S&&B(S)}catch(se){const te=se instanceof Error?se.message:s("errors.generic");N(ee?`${ee}: ${te}`:te)}finally{C("")}},de=()=>d("token",async()=>{const t=await l(`/api/projects/${o.id}/git/token`,{method:"PUT",body:JSON.stringify({token:T})});m(t.status),k(""),L(!1)},s("git.tokenSaved")),he=()=>d("remove-token",async()=>{const t=await l(`/api/projects/${o.id}/git/token`,{method:"DELETE"});m(t.status),k("")},s("git.tokenRemoved")),ge=()=>d("repository",async()=>{const t=await l(`/api/projects/${o.id}/git/repository`,{method:"POST",body:JSON.stringify({name:F,private:_})});m(t.status),A(!1)},s("git.repositoryCreated")),ue=()=>d("commit",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const t=await l(`/api/projects/${o.id}/git/commit`,{method:"POST",body:JSON.stringify({message:D})});m(t.status),Z(""),M(null),await z()},s("git.committed")),me=()=>d("push",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const t=await l(`/api/projects/${o.id}/git/push`,{method:"POST"});m(t.status),await z()},s("git.pushed"),s("git.pushFailed")),I=t=>d("diff",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));const c=t?`?revision=${encodeURIComponent(t)}`:"";M(await l(`/api/projects/${o.id}/git/diff${c}`))}),fe=async()=>{if(u){document.fullscreenElement&&await document.exitFullscreen().catch(()=>{}),x(!1);return}const t=O.current;if(t?.requestFullscreen)try{await t.requestFullscreen()}catch{}x(!0)},xe=()=>{const t=f;t&&(j(null),d("checkout",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));await l(`/api/projects/${o.id}/git/checkout`,{method:"POST",body:JSON.stringify({revision:t==="branch"?null:t.sha,force:R})}),window.location.reload()}))},pe=()=>{v(!1),d("discard",async()=>{if(!await r())throw new Error(s("errors.collaborationUnavailable"));await l(`/api/projects/${o.id}/git/discard`,{method:"POST"}),window.location.reload()})},je=a==="load"&&!i;return e.jsxs(e.Fragment,{children:[e.jsx(H,{open:g,extraWide:!0,className:u?"git-dialog-modal-fullscreen":void 0,title:s("git.title"),description:s("git.description"),onOpenChange:y,footer:e.jsx("button",{onClick:()=>y(!1),children:s("common.close")}),children:e.jsxs("div",{className:"git-dialog",children:[je&&e.jsxs("div",{className:"git-loading",children:[e.jsx(p,{className:"spin",size:22}),s("common.loading")]}),Q&&e.jsx("p",{className:"git-message error",children:Q}),X&&e.jsxs("p",{className:"git-message success",children:[e.jsx(q,{size:14}),X]}),i&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"git-section",children:[e.jsxs("button",{className:"git-section-toggle",type:"button","aria-expanded":$,onClick:()=>L(t=>!t),children:[e.jsxs("span",{className:"git-section-heading",children:[e.jsx(ze,{size:16}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.authentication")}),e.jsx("small",{children:i.tokenConfigured?s("git.connectedAs",{login:i.githubLogin}):s("git.authenticationHint")})]})]}),$?e.jsx(ie,{size:16}):e.jsx(ne,{size:16})]}),$&&e.jsxs("div",{className:"git-section-content",children:[e.jsxs("div",{className:"git-token-guidance",children:[e.jsx("strong",{children:s("git.recommendedAccessTitle")}),e.jsx("span",{children:s("git.recommendedAccess")})]}),i.tokenConfigured&&e.jsxs("div",{className:"git-account",children:[e.jsx(W,{size:17}),e.jsx("span",{children:s("git.connectedAs",{login:i.githubLogin})}),e.jsxs("button",{className:"danger-text",disabled:!!a,onClick:he,children:[e.jsx(ke,{size:14}),s("git.removeToken")]})]}),e.jsxs("label",{className:"form-field",children:[i.tokenConfigured?s("git.replaceToken"):s("git.token"),e.jsx("input",{type:"password",autoComplete:"new-password",spellCheck:!1,autoCapitalize:"none",autoCorrect:"off",value:T,placeholder:s("git.tokenPlaceholder"),onChange:t=>k(t.target.value)})]}),e.jsxs("div",{className:"git-inline-actions",children:[e.jsxs("a",{href:"https://github.com/settings/personal-access-tokens/new",target:"_blank",rel:"noreferrer",children:[s("git.createToken"),e.jsx(ae,{size:12})]}),e.jsxs("button",{disabled:!!a||T.trim().length<20,onClick:de,children:[a==="token"&&e.jsx(p,{className:"spin",size:13}),s("git.saveToken")]})]})]})]}),e.jsxs("section",{className:"git-section",children:[e.jsxs("button",{className:"git-section-toggle",type:"button","aria-expanded":P,onClick:()=>A(t=>!t),children:[e.jsxs("span",{className:"git-section-heading",children:[e.jsx(W,{size:16}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.repository")}),e.jsx("small",{children:i.remoteUrl?i.repositoryName??i.remoteUrl:i.initialized?s("git.localReady"):s("git.localPending")})]})]}),P?e.jsx(ie,{size:16}):e.jsx(ne,{size:16})]}),P&&e.jsx("div",{className:"git-section-content",children:i.remoteUrl?e.jsxs("div",{className:"git-repository-card",children:[e.jsxs("div",{children:[e.jsx("strong",{children:i.repositoryName??s("git.origin")}),e.jsxs("small",{children:[i.branch??s("git.detachedAt",{revision:i.latestCommit?.shortSha??"HEAD"})," · ",i.dirty?s("git.changedFiles",{count:i.changedFiles}):s("git.clean")]})]}),i.repositoryHtmlUrl&&e.jsxs("a",{href:i.repositoryHtmlUrl,target:"_blank",rel:"noreferrer",children:[e.jsx(ae,{size:14}),s("git.openGitHub")]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("label",{className:"form-field",children:[s("git.repositoryName"),e.jsx("input",{value:F,onChange:t=>K(t.target.value)})]}),e.jsxs("label",{className:"checkbox-field",children:[e.jsx("input",{type:"checkbox",checked:_,onChange:t=>ce(t.target.checked)}),s("git.privateRepository")]}),e.jsxs("button",{className:"git-primary-action",disabled:!!a||!i.tokenConfigured||!F.trim(),onClick:ge,children:[a==="repository"?e.jsx(p,{className:"spin",size:14}):e.jsx(W,{size:14}),s("git.createRepository")]})]})})]}),i.initialized&&e.jsxs("section",{className:"git-section",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(oe,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.commitAndPush")}),e.jsx("small",{children:i.branch?s("git.identity",{username:o.ownerUsername}):s("git.detachedHead")})]})]}),e.jsxs("label",{className:"form-field",children:[s("git.commitMessage"),e.jsx("textarea",{rows:3,value:D,placeholder:s("git.commitPlaceholder"),onChange:t=>Z(t.target.value)})]}),e.jsxs("div",{className:"git-action-row",children:[e.jsxs("button",{title:i.dirty?void 0:s("git.noChangesToCommit"),disabled:!!a||!i.branch||!i.dirty||!D.trim(),onClick:ue,children:[a==="commit"?e.jsx(p,{className:"spin",size:14}):e.jsx(oe,{size:14}),s("git.commit")]}),e.jsxs("button",{disabled:!!a||!i.branch||!i.tokenConfigured||!i.remoteUrl||!i.latestCommit,onClick:me,children:[a==="push"?e.jsx(p,{className:"spin",size:14}):e.jsx(we,{size:14}),s("git.push"),i.ahead>0&&e.jsx("span",{className:"git-count",children:i.ahead})]}),e.jsxs("button",{disabled:!!a||!i.latestCommit,onClick:()=>{I()},children:[a==="diff"?e.jsx(p,{className:"spin",size:14}):e.jsx(G,{size:14}),s("git.workingDiff")]}),e.jsxs("button",{className:"git-discard-button",title:i.restorable?s("git.discardChanges"):s("git.noChangesToDiscard"),disabled:!!a||!i.latestCommit||!i.restorable,onClick:()=>v(!0),children:[e.jsx(be,{size:14}),s("git.discardChanges")]}),!i.branch&&e.jsxs("button",{disabled:!!a,onClick:()=>{U(!1),j("branch")},children:[e.jsx(q,{size:14}),s("git.returnToBranch",{branch:i.defaultBranch})]}),e.jsxs("button",{disabled:!!a,onClick:()=>{d("refresh",z)},children:[e.jsx(Ee,{className:a==="refresh"?"spin":"",size:14}),s("git.refresh")]})]})]}),b&&e.jsxs("section",{ref:O,className:`git-section git-diff-section${u?" is-fullscreen":""}`,children:[e.jsxs("div",{className:"git-diff-heading",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(G,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.diffTitle")}),e.jsxs("small",{children:[b.title,b.truncated?` · ${s("git.diffTruncated")}`:""]})]})]}),e.jsxs("div",{className:"git-diff-controls",children:[e.jsxs("div",{className:"git-diff-font-controls",role:"group","aria-label":s("git.diffFontSize"),children:[e.jsx("button",{type:"button",className:"git-diff-font-button",disabled:w<=8,title:s("git.diffFontDecrease"),"aria-label":s("git.diffFontDecrease"),onClick:()=>Y(t=>Math.max(8,t-1)),children:e.jsx(ve,{size:14})}),e.jsxs("span",{className:"git-diff-font-value","aria-live":"polite",children:[w,"px"]}),e.jsx("button",{type:"button",className:"git-diff-font-button",disabled:w>=24,title:s("git.diffFontIncrease"),"aria-label":s("git.diffFontIncrease"),onClick:()=>Y(t=>Math.min(24,t+1)),children:e.jsx(Ce,{size:14})})]}),e.jsx("button",{type:"button",className:"git-diff-fullscreen",title:s(u?"git.exitFullscreenDiff":"git.fullscreenDiff"),"aria-label":s(u?"git.exitFullscreenDiff":"git.fullscreenDiff"),onClick:()=>{fe()},children:u?e.jsx(Se,{size:15}):e.jsx(Ne,{size:15})})]})]}),e.jsx(Fe,{content:b.diff,empty:s("git.noChanges"),fontSize:w})]}),i.initialized&&e.jsxs("section",{className:"git-section",children:[e.jsxs("div",{className:"git-section-heading",children:[e.jsx(q,{size:16}),e.jsxs("div",{children:[e.jsx("strong",{children:s("git.history")}),e.jsx("small",{children:s("git.historyHint")})]})]}),e.jsxs("div",{className:"git-history",children:[J.map(t=>e.jsxs("article",{children:[e.jsx("code",{children:t.shortSha}),e.jsxs("div",{children:[e.jsx("strong",{children:t.message}),e.jsxs("small",{children:[t.authorName," · ",new Date(t.authoredAt).toLocaleString(E.resolvedLanguage)]})]}),e.jsxs("span",{children:[e.jsx("button",{title:s("git.viewCommitDiff"),disabled:!!a,onClick:()=>{I(t.sha)},children:e.jsx(G,{size:14})}),e.jsx("button",{title:s("git.checkoutVersion"),disabled:!!a,onClick:()=>{U(!1),j(t)},children:e.jsx(Te,{size:14})})]})]},t.sha)),J.length===0&&e.jsx("p",{className:"muted",children:s("git.noCommits")})]})]})]})]})}),e.jsx(H,{open:!!f,title:s(f==="branch"?"git.returnTitle":"git.checkoutTitle"),description:f==="branch"?s("git.returnDescription",{branch:i?.defaultBranch??"main"}):s("git.checkoutDescription",{revision:f?.shortSha??"",message:f?.message??""}),onOpenChange:t=>{t||j(null)},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>j(null),children:s("common.cancel")}),e.jsx("button",{className:R?"danger":"primary",onClick:xe,children:s("git.checkout")})]}),children:e.jsxs("label",{className:"git-force-checkout",children:[e.jsx("input",{type:"checkbox",checked:R,onChange:t=>U(t.target.checked)}),e.jsxs("span",{children:[e.jsx("strong",{children:s("git.forceCheckout")}),e.jsx("small",{children:s("git.forceCheckoutDescription")})]})]})}),e.jsx(H,{open:le,title:s("git.discardTitle"),description:s("git.discardDescription"),onOpenChange:v,footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>v(!1),children:s("common.cancel")}),e.jsx("button",{className:"danger",onClick:pe,children:s("git.discardConfirm")})]}),children:e.jsx(e.Fragment,{})})]})}function Fe({content:g,empty:o,fontSize:y}){return g.trim()?e.jsx("pre",{className:"git-diff",style:{fontSize:`${y}px`},children:g.split(`
|
|
2
2
|
`).map((r,s)=>{const E=r.startsWith("+")&&!r.startsWith("+++")?"addition":r.startsWith("-")&&!r.startsWith("---")?"deletion":r.startsWith("@@")?"hunk":r.startsWith("diff ")?"header":"";return e.jsxs("span",{className:E,children:[r,`
|
|
3
3
|
`]},s)})}):e.jsx("div",{className:"git-diff-empty",children:o})}function re(g){return g.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"-").replace(/-+/g,"-").replace(/^[.-]+|[.-]+$/g,"").slice(0,100)||"texlite-project"}export{Ue as GitDialog};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as Z,r as a,j as e}from"./react-CsO_xvbl.js";import{d as ee}from"./index-rBWMBTI-.js";import{i as T,k as j,M as se,L as D,T as _,R as I,S as te,F as ie,l as q}from"./index-DbzKrh7H.js";const re=T("Clock3",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16.5 12",key:"1aq6pp"}]]);const F=T("FileClock",[["path",{d:"M16 22h2a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3",key:"37hlfg"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["circle",{cx:"8",cy:"16",r:"6",key:"10v15b"}],["path",{d:"M9.5 17.5 8 16.25V14",key:"1o80t2"}]]);const ae=T("HardDrive",[["line",{x1:"22",x2:"2",y1:"12",y2:"12",key:"1y58io"}],["path",{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"oot6mr"}],["line",{x1:"6",x2:"6.01",y1:"16",y2:"16",key:"sgf278"}],["line",{x1:"10",x2:"10.01",y1:"16",y2:"16",key:"1l4acy"}]]);const oe=T("Tag",[["path",{d:"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",key:"vktsd0"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor",key:"kqv944"}]]);function de({open:r,project:c,onOpenChange:M,onBeforeMutation:V}){const{t,i18n:L}=Z(),[v,N]=a.useState([]),[l,z]=a.useState(null),[x,S]=a.useState(""),[o,b]=a.useState(null),[C,P]=a.useState(""),[u,p]=a.useState(null),[B,H]=a.useState(""),[k,w]=a.useState(null),[m,$]=a.useState(null),[y,n]=a.useState(""),[R,d]=a.useState(""),E=c.permission!=="read",U=c.permission==="owner";a.useEffect(()=>{if(!r)return;const s=new AbortController;return n("load"),d(""),p(null),j(`/api/projects/${c.id}/history`,{signal:s.signal}).then(i=>{N(i.versions),z(i.stats),S(h=>i.versions.some(g=>g.id===h)?h:i.versions[0]?.id??"")}).catch(i=>{J(i)||d(f(i))}).finally(()=>{s.signal.aborted||n("")}),()=>s.abort()},[r,c.id]),a.useEffect(()=>{if(!r||!x){b(null);return}const s=new AbortController;return n("detail"),p(null),j(`/api/projects/${c.id}/history/${x}`,{signal:s.signal}).then(i=>{b(i),H(i.version.label??"");const h=i.version.changedPaths.find(g=>i.files.some(A=>A.path===g))??i.files[0]?.path??"";P(h)}).catch(i=>{J(i)||d(f(i))}).finally(()=>{s.signal.aborted||n("")}),()=>s.abort()},[r,c.id,x]);const G=async(s=C)=>{if(s){n("compare"),d("");try{p(await j(`/api/projects/${c.id}/history/${x}/file?path=${encodeURIComponent(s)}`))}catch(i){d(f(i))}finally{n("")}}},K=async()=>{if(o){n("label"),d("");try{const s=await j(`/api/projects/${c.id}/history/${o.version.id}`,{method:"PATCH",body:JSON.stringify({label:B.trim()||null})});b(i=>i&&{...i,version:s.version}),N(i=>i.map(h=>h.id===s.version.id?s.version:h)),z(s.stats)}catch(s){d(f(s))}finally{n("")}}},W=async()=>{const s=m;if(s){$(null),n("delete"),d("");try{if(!await V())throw new Error(t("errors.collaborationUnavailable"));const i=s==="all"?`/api/projects/${c.id}/history`:`/api/projects/${c.id}/history/${s.id}`,h=await j(i,{method:"DELETE"});if(z(h.stats),s==="all")N([]),S(""),b(null),p(null);else{const g=v.filter(A=>A.id!==s.id);N(g),x===s.id&&(S(g[0]?.id??""),b(null),p(null))}}catch(i){d(f(i))}finally{n("")}}},Q=async()=>{const s=k;if(!(!s||!o)){w(null),n("restore"),d("");try{if(!await V())throw new Error(t("errors.collaborationUnavailable"));await j(`/api/projects/${c.id}/history/${o.version.id}/restore`,{method:"POST",body:JSON.stringify(s==="project"?{}:{path:s})}),window.location.reload()}catch(i){d(f(i)),n("")}}},X=a.useMemo(()=>{if(!u)return[];const s=new ee;s.Diff_Timeout=1;const i=s.diff_main(u.historical,u.comparison,!0);return s.diff_cleanupSemantic(i),i},[u]),Y=m&&m!=="all"?m.label||new Date(m.createdAt).toLocaleString(L.resolvedLanguage):"";return e.jsxs(e.Fragment,{children:[e.jsx(se,{open:r,extraWide:!0,className:"history-dialog-modal",title:t("history.title"),description:t("history.description"),onOpenChange:M,footer:e.jsx("button",{onClick:()=>M(!1),children:t("common.close")}),children:e.jsxs("div",{className:"history-dialog",children:[R&&e.jsx("p",{className:"error history-message",children:R}),y==="load"&&v.length===0?e.jsxs("div",{className:"history-loading",children:[e.jsx(D,{className:"spin",size:22}),t("common.loading")]}):e.jsxs(e.Fragment,{children:[e.jsxs("aside",{className:"history-timeline",children:[U&&l&&e.jsxs("div",{className:`history-stats${l.storageLimitExceeded?" exceeded":""}`,children:[e.jsxs("header",{children:[e.jsxs("span",{children:[e.jsx(ae,{size:14}),t("history.storage")]}),e.jsx("button",{type:"button",disabled:!!y||v.length===0,title:t("history.clearAll"),"aria-label":t("history.clearAll"),onClick:()=>$("all"),children:e.jsx(_,{size:13})})]}),e.jsx("strong",{children:t("history.storageUsage",{used:O(l.objectBytes),limit:O(l.maxStorageBytes)})}),e.jsx("progress",{max:l.maxStorageBytes,value:Math.min(l.objectBytes,l.maxStorageBytes)}),e.jsxs("small",{children:[t("history.versionUsage",{count:l.ordinaryVersionCount,limit:l.maxVersions}),l.labeledVersionCount>0?` · ${t("history.protectedVersions",{count:l.labeledVersionCount})}`:""]}),l.storageLimitExceeded&&e.jsx("small",{className:"history-storage-warning",children:t("history.storageExceeded")})]}),v.map(s=>e.jsxs("button",{className:s.id===x?"active":"",onClick:()=>S(s.id),children:[e.jsx(F,{size:15}),e.jsxs("span",{children:[e.jsx("strong",{children:s.label||t(`history.reasons.${s.reason}`)}),e.jsx("small",{children:new Date(s.createdAt).toLocaleString(L.resolvedLanguage)}),e.jsxs("small",{children:[s.author?.name??t("editor.deletedUser")," · ",t("history.changedCount",{count:s.changedPaths.length})]})]})]},s.id)),v.length===0&&e.jsx("p",{className:"muted padded",children:t("history.empty")})]}),e.jsx("section",{className:"history-detail",children:o&&e.jsxs(e.Fragment,{children:[e.jsxs("header",{className:"history-detail-header",children:[e.jsxs("div",{children:[e.jsx("strong",{children:o.version.label||t(`history.reasons.${o.version.reason}`)}),e.jsxs("small",{children:[e.jsx(re,{size:12}),new Date(o.version.createdAt).toLocaleString(L.resolvedLanguage)," · ",t("history.fileCount",{count:o.version.fileCount})]})]}),e.jsxs("span",{className:"history-detail-actions",children:[U&&e.jsxs("button",{className:"danger-text",disabled:!!y,onClick:()=>$(o.version),children:[e.jsx(_,{size:14}),t("history.deleteVersion")]}),E&&e.jsxs("button",{className:"danger-text",disabled:!!y,onClick:()=>w("project"),children:[e.jsx(I,{size:14}),t("history.restoreProject")]})]})]}),E&&e.jsxs("div",{className:"history-label",children:[e.jsxs("label",{children:[e.jsx(oe,{size:14}),e.jsx("input",{value:B,maxLength:80,placeholder:t("history.labelPlaceholder"),onChange:s=>H(s.target.value)})]}),e.jsxs("button",{disabled:y==="label"||B.trim()===(o.version.label??""),onClick:()=>{K()},children:[y==="label"?e.jsx(D,{className:"spin",size:13}):e.jsx(te,{size:13}),t("history.saveLabel")]})]}),e.jsxs("div",{className:"history-files",children:[e.jsx("div",{className:"history-file-list",children:o.files.map(s=>{const i=o.version.changedPaths.includes(s.path);return e.jsxs("button",{className:s.path===C?"active":"",title:s.path,onClick:()=>{P(s.path),p(null)},children:[e.jsx(ie,{size:13}),e.jsx("span",{children:s.path}),i&&e.jsx("small",{children:t("history.changed")})]},s.path)})}),e.jsxs("div",{className:"history-file-actions",children:[e.jsxs("button",{disabled:!C||!!y,onClick:()=>{G()},children:[y==="compare"?e.jsx(D,{className:"spin",size:13}):e.jsx(F,{size:13}),t("history.compareCurrent")]}),E&&e.jsxs("button",{disabled:!C||!!y,onClick:()=>w(C),children:[e.jsx(I,{size:13}),t("history.restoreFile")]})]})]}),e.jsx("div",{className:"history-comparison",children:u?e.jsxs(e.Fragment,{children:[e.jsxs("header",{children:[e.jsx("strong",{children:u.path}),e.jsx("small",{children:t("history.comparingCurrent")})]}),e.jsx("pre",{children:X.map(([s,i],h)=>e.jsx("span",{className:s<0?"history-removed":s>0?"history-added":"",children:i},h))})]}):e.jsxs("div",{className:"history-comparison-empty",children:[e.jsx(F,{size:25}),e.jsx("span",{children:t("history.chooseFile")})]})})]})})]})]})}),e.jsx(q,{open:!!k,title:t(k==="project"?"history.restoreProjectTitle":"history.restoreFileTitle"),description:k==="project"?t("history.restoreProjectDescription"):t("history.restoreFileDescription",{path:k??""}),confirmLabel:t("history.restore"),danger:!0,onCancel:()=>w(null),onConfirm:()=>{Q()}}),e.jsx(q,{open:!!m,title:t(m==="all"?"history.clearAllTitle":"history.deleteVersionTitle"),description:m==="all"?t("history.clearAllDescription"):t("history.deleteVersionDescription",{name:Y}),confirmLabel:t(m==="all"?"history.clearAll":"history.deleteVersion"),danger:!0,onCancel:()=>$(null),onConfirm:()=>{W()}})]})}function O(r){return r<1024?`${r} B`:r<1024*1024?`${Math.round(r/102.4)/10} KB`:r<1024*1024*1024?`${Math.round(r/(1024*1024)*10)/10} MB`:`${Math.round(r/(1024*1024*1024)*10)/10} GB`}function f(r){return r instanceof Error?r.message:String(r)}function J(r){return r instanceof Error&&r.name==="AbortError"}export{de as HistoryDialog};
|