texlite 0.2.0
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/LICENSE +661 -0
- package/README.md +403 -0
- package/README.zh-CN.md +391 -0
- package/dist/client/assets/GitDialog-BHHxSBn3.js +3 -0
- package/dist/client/assets/HistoryDialog-Dlv57tYf.js +5 -0
- package/dist/client/assets/LatexEditor-B-0VRUPv.js +55 -0
- package/dist/client/assets/PdfPreview-617TWFJN.js +1 -0
- package/dist/client/assets/ProjectNavigationDialogs-Cpt2LVEt.js +1 -0
- package/dist/client/assets/SystemMetricsDialog-fkFHVVzJ.js +1 -0
- package/dist/client/assets/editor-JxT6uSYX.js +14 -0
- package/dist/client/assets/index-Bc5CmFMl.js +15 -0
- package/dist/client/assets/index-CMqYAbDx.css +1 -0
- package/dist/client/assets/pdf-DPnwNQHb.js +55 -0
- package/dist/client/assets/pdf.worker.min-CHFwMXne.mjs +29 -0
- package/dist/client/assets/plus-DzbLkp6l.js +1 -0
- package/dist/client/assets/react-CsO_xvbl.js +49 -0
- package/dist/client/assets/spellCheck-R6fUM9oA.js +49783 -0
- package/dist/client/index.html +20 -0
- package/dist/client/logo.svg +4 -0
- package/dist/server/anchors.js +95 -0
- package/dist/server/app.js +1624 -0
- package/dist/server/archive.js +26 -0
- package/dist/server/auth.js +42 -0
- package/dist/server/cli.js +318 -0
- package/dist/server/collaboration.js +635 -0
- package/dist/server/compileArtifacts.js +86 -0
- package/dist/server/compileDiagnostics.js +227 -0
- package/dist/server/compiler.js +514 -0
- package/dist/server/config.js +336 -0
- package/dist/server/db.js +280 -0
- package/dist/server/environment.js +57 -0
- package/dist/server/files.js +121 -0
- package/dist/server/git.js +414 -0
- package/dist/server/history.js +379 -0
- package/dist/server/http.js +7 -0
- package/dist/server/index.js +75 -0
- package/dist/server/latexCompletion.js +296 -0
- package/dist/server/metrics.js +34 -0
- package/dist/server/pm2.js +234 -0
- package/dist/server/projectOutline.js +101 -0
- package/dist/server/projectSearch.js +112 -0
- package/dist/server/projects.js +29 -0
- package/dist/server/routes/compile.js +343 -0
- package/dist/server/runtimePaths.js +42 -0
- package/dist/server/security.js +29 -0
- package/dist/server/spellCheck.js +195 -0
- package/dist/server/synctex.js +56 -0
- package/dist/server/zip.js +185 -0
- package/package.json +110 -0
- package/preview.png +0 -0
- package/texlite.config.example.json +33 -0
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# texLite
|
|
2
|
+
|
|
3
|
+
texLite 是一个轻量、以本机为中心的 LaTeX 网页工作区,用于编写、编译、预览和讨论 LaTeX 文档。它面向同一台服务器上的少量可信用户。texLite 使用服务器已经安装的 LaTeX 环境,不打包 LaTeX 镜像;其余技术栈也尽量保持简单。
|
|
4
|
+
|
|
5
|
+
**文档:** [English](README.md) · 简体中文(当前文件)
|
|
6
|
+
|
|
7
|
+
[](https://github.com/SWUFE-DB-Group/TexLite/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## 设计目标
|
|
12
|
+
|
|
13
|
+
- 使用宿主机已有的 TeX Live/LaTeX,使 LaTeX 可以独立更新。
|
|
14
|
+
- 默认不依赖 Redis、MongoDB、Caddy 或 Nginx,适合 localhost 部署。
|
|
15
|
+
- 项目源码和编译产物保存在本地文件系统。
|
|
16
|
+
- 使用 better-sqlite3 访问 SQLite,保存持久化应用数据。
|
|
17
|
+
- 在不引入分布式部署的前提下,为少量并发会话提供实用的协作能力。
|
|
18
|
+
|
|
19
|
+
## 功能
|
|
20
|
+
|
|
21
|
+
- 管理员初始化和用户管理;不开放公众注册。
|
|
22
|
+
- 用户独立的“创建项目”权限,新用户默认关闭。
|
|
23
|
+
- 项目、文件夹、文件和资源管理,支持拖拽上传。
|
|
24
|
+
- ZIP 项目导入、安全解压和主文档自动选择。
|
|
25
|
+
- 项目列表/网格视图、搜索、按最后修改时间或创建时间排序,以及每个用户私有的 Finder 风格彩色标签。
|
|
26
|
+
- 项目重命名、源码 ZIP 下载、删除,以及所有者和修改信息展示。
|
|
27
|
+
- 中文和英文界面,自动识别浏览器语言,并可手动切换。
|
|
28
|
+
- 基于 CodeMirror 的 LaTeX 编辑器:语法高亮、折叠、补全、快速打开文件、当前文件及全项目搜索/替换、跨文件大纲和外观设置。
|
|
29
|
+
- 基于 Yjs CRDT 的协作编辑,显示活动会话头像、远程光标,保存状态以服务端持久化确认为准,并用浏览器 IndexedDB 保留断线草稿;单个项目建议不超过约 10 个并发会话。
|
|
30
|
+
- 内容寻址的自动项目历史:可添加版本标签、比较文件、恢复单个文件或带安全检查点地恢复整个项目。
|
|
31
|
+
- 源码范围批注、回复、resolved 状态、用户/时间信息,以及源码变化后的批注锚点重映射。
|
|
32
|
+
- 使用宿主机 latexmk 编译,支持 pdflatex、xelatex、lualatex、项目级 latexmkrc、项目编译串行化、持久增量缓存、不可变发布快照、最新 PDF 保留、SyncTeX 跳转、可点击的结构化诊断和 .bbl 等编译产物下载。
|
|
33
|
+
- 项目共享支持只读和读写权限;只读用户仍然可以添加和回复批注。
|
|
34
|
+
- 项目所有者专用的本地 Git 历史和 GitHub 备份:commit、push、diff、checkout 和恢复已跟踪修改。
|
|
35
|
+
- 可选 PM2 进程管理,支持异常重启、状态查看和日志管理。
|
|
36
|
+
- 仅管理员可查看的系统状态:编译队列、协作会话、事件循环延迟、内存和近期操作耗时。
|
|
37
|
+
|
|
38
|
+
## 技术结构
|
|
39
|
+
|
|
40
|
+
| 部分 | 实现 |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| 浏览器界面 | React、Vite、CodeMirror、PDF.js |
|
|
43
|
+
| API 和静态服务 | Fastify、WebSocket |
|
|
44
|
+
| 协作 | Yjs、y-websocket、y-codemirror.next |
|
|
45
|
+
| 数据库 | better-sqlite3 访问 SQLite |
|
|
46
|
+
| 文件 | 配置数据目录下的本地项目目录 |
|
|
47
|
+
| 编译 | 宿主机 latexmk 和配置的 LaTeX 引擎 |
|
|
48
|
+
| Git 备份 | 宿主机 git 和 GitHub REST API |
|
|
49
|
+
|
|
50
|
+
texLite 按单个 Node.js 进程设计。不要让多个应用实例同时使用同一个 SQLite 数据库或项目目录;协作状态、编译队列和文件系统都是单进程资源。
|
|
51
|
+
|
|
52
|
+
## 环境要求
|
|
53
|
+
|
|
54
|
+
- Node.js 24 或更高版本
|
|
55
|
+
- npm
|
|
56
|
+
- git(可选;仅项目所有者使用 Git/GitHub 集成时需要)
|
|
57
|
+
- latexmk
|
|
58
|
+
- 至少一个配置好的引擎:pdflatex、xelatex 或 lualatex
|
|
59
|
+
|
|
60
|
+
初始化前可以检查:
|
|
61
|
+
|
|
62
|
+
~~~bash
|
|
63
|
+
node --version
|
|
64
|
+
npm --version
|
|
65
|
+
latexmk --version
|
|
66
|
+
xelatex --version
|
|
67
|
+
# 可选,仅在需要 Git/GitHub 集成时检查:
|
|
68
|
+
git --version
|
|
69
|
+
~~~
|
|
70
|
+
|
|
71
|
+
npm run init 和应用启动会检查 latexmk 以及 latex.allowedEngines 中列出的每个引擎。Git 不参与这项核心检查,因此未安装 Git 的宿主机仍可正常初始化并运行 TexLite。项目所有者打开 Git 面板或执行 Git/GitHub 操作时,TexLite 才会按需检查 git.binary;如果 Git 不可用,界面会给出可操作的错误提示。
|
|
72
|
+
|
|
73
|
+
## 快速开始
|
|
74
|
+
|
|
75
|
+
### 全局 npm 安装
|
|
76
|
+
|
|
77
|
+
发布后的 npm 包提供 `texlite` 命令,配置和项目数据不会写入全局 npm
|
|
78
|
+
安装目录:
|
|
79
|
+
|
|
80
|
+
~~~bash
|
|
81
|
+
npm install --global texlite
|
|
82
|
+
texlite init
|
|
83
|
+
texlite start
|
|
84
|
+
texlite status
|
|
85
|
+
~~~
|
|
86
|
+
|
|
87
|
+
打开 http://127.0.0.1:3000。可以从任意工作目录管理服务:
|
|
88
|
+
|
|
89
|
+
~~~bash
|
|
90
|
+
texlite stop
|
|
91
|
+
texlite restart
|
|
92
|
+
texlite logs
|
|
93
|
+
~~~
|
|
94
|
+
|
|
95
|
+
默认配置文件为
|
|
96
|
+
`$XDG_CONFIG_HOME/texlite/texlite.config.json`;如果没有设置
|
|
97
|
+
`XDG_CONFIG_HOME`,则为 `~/.config/texlite/texlite.config.json`。默认数据目录为
|
|
98
|
+
`$XDG_DATA_HOME/texlite`;如果没有设置 `XDG_DATA_HOME`,则为
|
|
99
|
+
`~/.local/share/texlite`。可以使用 `--config PATH` 或 `TEXLITE_CONFIG`
|
|
100
|
+
指定其他配置文件。
|
|
101
|
+
|
|
102
|
+
`texlite serve` 以前台方式运行,适合 Docker、systemd 和调试。
|
|
103
|
+
`start`、`status`、`stop`、`restart` 和 `logs` 使用 npm 包内置的 PM2。
|
|
104
|
+
|
|
105
|
+
升级时可以执行:
|
|
106
|
+
|
|
107
|
+
~~~bash
|
|
108
|
+
npm update --global texlite
|
|
109
|
+
texlite restart
|
|
110
|
+
~~~
|
|
111
|
+
|
|
112
|
+
卸载 npm 包不会删除配置和项目数据:
|
|
113
|
+
|
|
114
|
+
~~~bash
|
|
115
|
+
npm uninstall --global texlite
|
|
116
|
+
~~~
|
|
117
|
+
|
|
118
|
+
### 从仓库/源码运行
|
|
119
|
+
|
|
120
|
+
~~~bash
|
|
121
|
+
npm ci
|
|
122
|
+
cp texlite.config.example.json texlite.config.json
|
|
123
|
+
export TEXLITE_CONFIG="$PWD/texlite.config.json"
|
|
124
|
+
# 按需要编辑 texlite.config.json
|
|
125
|
+
npm run init
|
|
126
|
+
npm run build
|
|
127
|
+
npm start
|
|
128
|
+
~~~
|
|
129
|
+
|
|
130
|
+
默认只监听 localhost,也不开放公众注册。初始化命令会要求创建第一个
|
|
131
|
+
管理员;没有有效管理员时服务不会启动。
|
|
132
|
+
|
|
133
|
+
如果需要非交互初始化,可以只为该命令设置以下环境变量:
|
|
134
|
+
|
|
135
|
+
~~~bash
|
|
136
|
+
TEXLITE_INIT_USERNAME=admin \
|
|
137
|
+
TEXLITE_INIT_DISPLAY_NAME=Administrator \
|
|
138
|
+
TEXLITE_INIT_PASSWORD='至少 8 个字符的密码' \
|
|
139
|
+
npm run init
|
|
140
|
+
~~~
|
|
141
|
+
|
|
142
|
+
在共享机器上不要让密码进入 shell 历史记录。
|
|
143
|
+
|
|
144
|
+
## 开发和验证
|
|
145
|
+
|
|
146
|
+
在两个终端分别运行 API/server 和 Vite:
|
|
147
|
+
|
|
148
|
+
~~~bash
|
|
149
|
+
npm run dev # API/server:http://127.0.0.1:3000
|
|
150
|
+
npm run dev:web # Vite 界面:http://127.0.0.1:5173
|
|
151
|
+
~~~
|
|
152
|
+
|
|
153
|
+
Vite 会把 /api 请求代理到后端。生产式运行前可以执行:
|
|
154
|
+
|
|
155
|
+
~~~bash
|
|
156
|
+
npm run typecheck
|
|
157
|
+
npm test
|
|
158
|
+
npm run build
|
|
159
|
+
npm start
|
|
160
|
+
~~~
|
|
161
|
+
|
|
162
|
+
## 配置
|
|
163
|
+
|
|
164
|
+
配置文件路径优先级如下:
|
|
165
|
+
|
|
166
|
+
1. `texlite --config PATH`;
|
|
167
|
+
2. `TEXLITE_CONFIG`;
|
|
168
|
+
3. `$XDG_CONFIG_HOME/texlite/texlite.config.json`;
|
|
169
|
+
4. `~/.config/texlite/texlite.config.json`。
|
|
170
|
+
|
|
171
|
+
配置中的相对路径以配置文件所在目录为基准。`storage.dataDir` 默认使用
|
|
172
|
+
`$XDG_DATA_HOME/texlite`;未设置时使用 `~/.local/share/texlite`。可以在配置中
|
|
173
|
+
设置 `storage.dataDir`,或使用 `TEXLITE_DATA_DIR` 指定其他数据目录。生产环境
|
|
174
|
+
的前端资源默认定位到 npm 包内部的 `dist/client`,开发或自定义部署可以用
|
|
175
|
+
`TEXLITE_CLIENT_DIR` 覆盖。
|
|
176
|
+
|
|
177
|
+
示例配置:
|
|
178
|
+
|
|
179
|
+
~~~json
|
|
180
|
+
{
|
|
181
|
+
"siteName": "TexLite",
|
|
182
|
+
"adminEmail": "admin@example.com",
|
|
183
|
+
"sessionDays": 14,
|
|
184
|
+
"server": { "host": "127.0.0.1", "port": 3000 },
|
|
185
|
+
"storage": { "dataDir": ".texlite" },
|
|
186
|
+
"uploads": { "maxFileSizeMB": 50 },
|
|
187
|
+
"history": { "maxVersions": 200, "maxStorageMB": 512 },
|
|
188
|
+
"git": {
|
|
189
|
+
"binary": "git",
|
|
190
|
+
"operationTimeoutSeconds": 120,
|
|
191
|
+
"githubApiBaseUrl": "https://api.github.com"
|
|
192
|
+
},
|
|
193
|
+
"latex": {
|
|
194
|
+
"latexmk": "latexmk",
|
|
195
|
+
"defaultEngine": "xelatex",
|
|
196
|
+
"allowedEngines": ["pdflatex", "xelatex", "lualatex"],
|
|
197
|
+
"extraArgs": [],
|
|
198
|
+
"allowProjectLatexmkrc": true,
|
|
199
|
+
"compileTimeoutSeconds": 600,
|
|
200
|
+
"maxCompileJobs": 10
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
~~~
|
|
204
|
+
|
|
205
|
+
仓库中的示例为了方便源码开发,显式使用 `.texlite`。`texlite init` 自动生成
|
|
206
|
+
的 npm 配置则使用前面所述的 XDG 数据目录默认值。
|
|
207
|
+
|
|
208
|
+
重要配置:
|
|
209
|
+
|
|
210
|
+
- server.host、server.port:监听地址和端口。除非已经准备好安全部署,否则保持 127.0.0.1。
|
|
211
|
+
- storage.dataDir:SQLite 数据库、项目源码、编译输出和 Git token 加密密钥的位置。
|
|
212
|
+
- uploads.maxFileSizeMB:项目上传、ZIP 条目、项目文件和资源的最大大小,默认 50 MB。
|
|
213
|
+
- history.maxVersions:每个项目最多保留的普通、未标记版本数;初始版本和已标记版本受保护。
|
|
214
|
+
- history.maxStorageMB:每个项目去重历史对象的软上限。超过后优先删除最旧的普通版本;受保护版本和当前内部基线可使实际用量超过上限。
|
|
215
|
+
- latex.defaultEngine、latex.allowedEngines、latex.extraArgs:界面中可选择的编译方式。
|
|
216
|
+
- latex.compileTimeoutSeconds:单次编译超时时间。
|
|
217
|
+
- latex.maxCompileJobs:全局 LaTeX 进程并发上限。同一项目的同一主文档串行编译;多个源码版本排队时只保留最新请求。不同主文档使用独立工作区,可在该全局上限内并行编译。
|
|
218
|
+
- latex.allowProjectLatexmkrc:是否允许项目提供多行 latexmkrc。该文件是可执行的 Perl 配置,只建议对可信用户开启。
|
|
219
|
+
|
|
220
|
+
### 生效默认值和启动校验
|
|
221
|
+
|
|
222
|
+
如果没有设置某项,texLite 使用以下内置默认值(自动生成的示例配置
|
|
223
|
+
可以显式写出例如管理员邮箱这样的值):
|
|
224
|
+
|
|
225
|
+
| 配置项 | 生效默认值 |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| `siteName` | `TexLite` |
|
|
228
|
+
| `adminEmail` | 空 |
|
|
229
|
+
| `server.host` / `server.port` | `127.0.0.1` / `3000` |
|
|
230
|
+
| `storage.dataDir` | `$XDG_DATA_HOME/texlite` 或 `~/.local/share/texlite` |
|
|
231
|
+
| `clientDir` | npm 包内部的 `dist/client` |
|
|
232
|
+
| `sessionDays` | `14` 天 |
|
|
233
|
+
| `uploads.maxFileSizeMB` | `50` MB |
|
|
234
|
+
| `history.maxVersions` | 每项目 `200` 个普通版本 |
|
|
235
|
+
| `history.maxStorageMB` | 每项目 `512` MB(软上限) |
|
|
236
|
+
| `latex.latexmk` | `latexmk` |
|
|
237
|
+
| `latex.defaultEngine` | `xelatex` |
|
|
238
|
+
| `latex.allowedEngines` | `pdflatex`、`xelatex`、`lualatex` |
|
|
239
|
+
| `latex.extraArgs` | `[]` |
|
|
240
|
+
| `latex.allowProjectLatexmkrc` | `true` |
|
|
241
|
+
| `latex.compileTimeoutSeconds` | `600` 秒 |
|
|
242
|
+
| `latex.maxCompileJobs` | `10` |
|
|
243
|
+
| `git.binary` / `git.operationTimeoutSeconds` | `git` / `120` 秒 |
|
|
244
|
+
| `git.githubApiBaseUrl` | `https://api.github.com` |
|
|
245
|
+
|
|
246
|
+
配置会在环境检查、打开数据库和启动 HTTP 监听之前完成校验。显式设置的
|
|
247
|
+
非法值不会静默回退到默认值。允许范围为:端口 `1–65535`,会话有效期
|
|
248
|
+
`1–3650` 天,单文件大小 `1–2048` MB,历史版本数 `10–5000`,历史存储
|
|
249
|
+
`16–102400` MB,编译超时 `1–3600` 秒,编译并发数 `1–32`,Git 超时
|
|
250
|
+
`1–3600` 秒。引擎名称必须受支持且不能重复;允许引擎
|
|
251
|
+
列表必须包含选中的默认引擎。数据目录和项目目录不能指向文件,数据目录
|
|
252
|
+
不能是文件系统根目录;数据目录不存在时,其父目录必须已经存在且可写。
|
|
253
|
+
GitHub API 地址必须是 `http://` 或 `https://` URL。
|
|
254
|
+
|
|
255
|
+
JSON 类型错误、必填字符串为空、整数格式错误(包括带小数或非数字的环境
|
|
256
|
+
变量)、不支持的引擎、非法 URL 和不可用路径都会停止启动,并显示配置项、
|
|
257
|
+
期望格式及修复提示。`npm run init` 也执行同样的校验,因此可以在创建第一个
|
|
258
|
+
管理员前先发现配置问题。
|
|
259
|
+
|
|
260
|
+
环境变量会覆盖配置文件中的对应值:
|
|
261
|
+
|
|
262
|
+
~~~text
|
|
263
|
+
TEXLITE_CONFIG
|
|
264
|
+
XDG_CONFIG_HOME XDG_DATA_HOME
|
|
265
|
+
TEXLITE_SITE_NAME TEXLITE_ADMIN_EMAIL
|
|
266
|
+
TEXLITE_HOST TEXLITE_PORT
|
|
267
|
+
TEXLITE_DATA_DIR TEXLITE_CLIENT_DIR
|
|
268
|
+
TEXLITE_SESSION_DAYS TEXLITE_MAX_UPLOAD_SIZE_MB
|
|
269
|
+
TEXLITE_HISTORY_MAX_VERSIONS TEXLITE_HISTORY_MAX_STORAGE_MB
|
|
270
|
+
TEXLITE_LATEXMK TEXLITE_DEFAULT_ENGINE
|
|
271
|
+
TEXLITE_COMPILE_TIMEOUT TEXLITE_MAX_COMPILE_JOBS
|
|
272
|
+
TEXLITE_GIT TEXLITE_GIT_TIMEOUT
|
|
273
|
+
TEXLITE_GITHUB_API_URL
|
|
274
|
+
~~~
|
|
275
|
+
|
|
276
|
+
texLite 不执行 tlmgr,也不会自动安装缺失宏包。宿主机更新 TeX Live 后,下一次编译会直接使用新环境。
|
|
277
|
+
|
|
278
|
+
## 服务管理
|
|
279
|
+
|
|
280
|
+
通过全局 npm 安装时,生命周期命令使用包内置的 PM2,不需要额外全局安装:
|
|
281
|
+
|
|
282
|
+
~~~bash
|
|
283
|
+
texlite start
|
|
284
|
+
texlite status
|
|
285
|
+
texlite logs
|
|
286
|
+
texlite restart
|
|
287
|
+
texlite stop
|
|
288
|
+
~~~
|
|
289
|
+
|
|
290
|
+
`texlite doctor` 会校验配置、路径、LaTeX 和管理员;使用 `--git` 可以额外
|
|
291
|
+
检查可选的 Git 集成。`texlite config` 显示生效的配置和路径。
|
|
292
|
+
`texlite serve` 以前台方式运行,不启动 PM2。
|
|
293
|
+
|
|
294
|
+
从仓库运行时仍可以使用 `ecosystem.config.cjs` 和 npm PM2 快捷命令。
|
|
295
|
+
该配置明确使用一个 fork 实例(`instances: 1`)。不支持 cluster 模式,因为
|
|
296
|
+
协作状态、编译队列、SQLite 连接和项目文件系统都属于单个进程。
|
|
297
|
+
|
|
298
|
+
在服务器上安装一次 PM2,构建后启动:
|
|
299
|
+
|
|
300
|
+
~~~bash
|
|
301
|
+
npm install --global pm2
|
|
302
|
+
npm run build
|
|
303
|
+
pm2 start ecosystem.config.cjs
|
|
304
|
+
pm2 status
|
|
305
|
+
pm2 logs texlite
|
|
306
|
+
~~~
|
|
307
|
+
|
|
308
|
+
对应的 npm 快捷命令是 npm run pm2:start、npm run pm2:restart、npm run pm2:stop、npm run pm2:delete、npm run pm2:logs 和 npm run pm2:save。
|
|
309
|
+
|
|
310
|
+
发布新代码后:
|
|
311
|
+
|
|
312
|
+
~~~bash
|
|
313
|
+
npm run build
|
|
314
|
+
pm2 restart texlite --update-env
|
|
315
|
+
~~~
|
|
316
|
+
|
|
317
|
+
如果需要开机自动恢复,执行 pm2 startup 输出的命令,然后保存进程列表:
|
|
318
|
+
|
|
319
|
+
~~~bash
|
|
320
|
+
pm2 save
|
|
321
|
+
~~~
|
|
322
|
+
|
|
323
|
+
常用命令还有 pm2 stop texlite、pm2 restart texlite、pm2 delete texlite 和 pm2 monit。
|
|
324
|
+
|
|
325
|
+
## 协作和编译模型
|
|
326
|
+
|
|
327
|
+
编辑器使用 Yjs CRDT 支持并发编辑。项目顶部显示活动浏览器会话,远程光标使用不同颜色;单个项目的目标规模约为 10 个活动会话。只有服务端确认源码已经持久化后,保存状态才会显示“已保存”。尚未同步的更新也会保留在浏览器 IndexedDB 中,并在临时断线后恢复;恢复历史版本或 Git checkout 会轮换协作 epoch,避免旧的本地草稿覆盖刚恢复的源码树。删除或移动文件后,其他会话仍绑定旧编辑器时产生的迟到修改会被拒绝。
|
|
328
|
+
|
|
329
|
+
项目设置中保存的主文档是默认主文档。用户打开另一个包含 `\documentclass` 的 `.tex` 文件后,该文件仅在当前浏览器会话中成为编译主文档;打开被引用的普通片段不会切换主文档。系统只编译当前主文档。编译状态、日志、保留的 PDF、编译产物、大纲和 SyncTeX 均按主文档隔离,因此查看不同主文档的协作者不会看到彼此的编译提示,也不会覆盖彼此的 PDF。
|
|
330
|
+
|
|
331
|
+
编译与编辑相互隔离:
|
|
332
|
+
|
|
333
|
+
1. 服务端捕获不可变源码快照。
|
|
334
|
+
2. 将有变化的文件增量同步到按项目、主文档和编译设置区分的持久编译工作区。
|
|
335
|
+
3. latexmk 复用依赖数据库和辅助文件;同一主文档的任务仍然串行执行。
|
|
336
|
+
4. 将 PDF、日志、SyncTeX 和其他产物复制到不可变的运行快照。
|
|
337
|
+
5. 编译成功后,以原子方式发布最新产物快照。
|
|
338
|
+
|
|
339
|
+
同一主文档的可变缓存不会被并发使用,不同主文档使用独立缓存,已经发布的产物也不会被原地修改。编译时仍可查看旧 PDF;BibTeX 文档所需的多轮编译由 latexmk 自动处理,参考文献输入未变化时不会重复运行 BibTeX。编译响应提供 Server-Timing 响应头,可以分别查看快照、缓存同步、latexmk、产物复制和请求总耗时。TexLite 不保留可浏览的编译历史:仅按主文档保存最近一次尝试的状态,以及仍用于 PDF 预览的最近成功结果(如果需要)。更早的数据库记录和不可变产物包会自动清理。
|
|
340
|
+
|
|
341
|
+
## 历史、导航和诊断
|
|
342
|
+
|
|
343
|
+
自动历史会记录源码/文件操作、编译器设置、Git 操作、恢复操作和服务端确认的协作保存。协作保存采用固定的两分钟版本窗口,因此连续编辑仍会形成有用的恢复点,同时不会按每次按键记录。TexLite 默认保留最近 200 个普通版本,以及全部已标记版本和初始版本。文件内容以完整的 SHA-256 对象保存:未变化内容会复用,变化文件则产生一个新的完整对象。默认每个项目设置 512 MB 软上限,超过后优先删除最旧的普通版本;受保护版本和当前内部基线可能使实际用量超过上限。项目所有者可以查看历史存储用量、删除单个版本或清空全部历史,而不会改变当前项目文件。不再被引用的对象会被清理。历史功能适合修正写作误操作,但不能替代对完整数据目录的备份。
|
|
344
|
+
|
|
345
|
+
Ctrl/Cmd+P 用于快速打开项目文件,Ctrl/Cmd+Shift+F 用于全项目纯文本搜索和替换。全项目替换按一次操作暂存,并自动创建历史版本。大纲从主文档开始跟随 `\\input`、`\\include` 和 `\\subfile`。结构化警告/错误会解析项目内文件名,点击后直接跳到源码行;完整 latexmk 原始日志仍然保留。
|
|
346
|
+
|
|
347
|
+
管理员可在项目列表打开“系统状态”。指标只包含数量和耗时,绝不包含源码、密码、Git token 或批注;也可通过需要管理员登录的 `GET /api/health/metrics` 获取。
|
|
348
|
+
|
|
349
|
+
## GitHub 备份
|
|
350
|
+
|
|
351
|
+
Git 面板仅对项目所有者显示。GitHub personal access token 按项目配置并加密保存到 SQLite。本地仓库位于项目源码目录;每次命令临时使用以下身份:
|
|
352
|
+
|
|
353
|
+
~~~text
|
|
354
|
+
user.name = 项目所有者 username
|
|
355
|
+
user.email = <username>@texlite.com
|
|
356
|
+
~~~
|
|
357
|
+
|
|
358
|
+
对于 fine-grained token,在可信用户部署中建议授予仓库 Administration 和 Contents 的读写权限,并选择 All repositories。配置 token 时远程仓库可能还不存在,之后也可能创建新仓库。token 不会出现在远程 URL 或命令行参数中。
|
|
359
|
+
|
|
360
|
+
普通 checkout 遵循 Git 的默认保留行为,冲突时拒绝操作。只有在确认框中明确选择强制选项,才会丢弃已跟踪、未跟踪和 ignored 的工作区文件。checkout 历史版本后,需要先返回默认分支才能提交或 push。
|
|
361
|
+
|
|
362
|
+
## 数据、备份和删除
|
|
363
|
+
|
|
364
|
+
默认数据目录:
|
|
365
|
+
|
|
366
|
+
~~~text
|
|
367
|
+
.texlite/
|
|
368
|
+
├── texlite.db
|
|
369
|
+
├── texlite.db-wal
|
|
370
|
+
├── texlite.db-shm
|
|
371
|
+
├── git-token.key
|
|
372
|
+
└── projects/
|
|
373
|
+
└── <project-id>/
|
|
374
|
+
├── source/
|
|
375
|
+
└── output/
|
|
376
|
+
└── .texlite/ # 编译缓存/运行和历史对象
|
|
377
|
+
~~~
|
|
378
|
+
|
|
379
|
+
请同时备份 texlite.db、git-token.key 和 projects/。恢复已保存的 GitHub token 必须保留加密密钥。在线备份 SQLite 文件时也要包含 WAL 文件,或者使用 SQLite 感知的备份方式。
|
|
380
|
+
|
|
381
|
+
删除用户会删除其成员关系,但既有批注仍会保留,并显示为“已删除用户”。根据管理员选择,该用户拥有的项目可以转移给当前管理员,也可以连同文件一起删除。最后一个有效管理员不能被删除、禁用或降级。
|
|
382
|
+
|
|
383
|
+
## 安全边界
|
|
384
|
+
|
|
385
|
+
texLite 面向 localhost 上的可信用户。默认编译关闭 shell escape、不通过 shell 拼接命令,并限制编译超时和并发数量。但 LaTeX 本身以及项目 latexmkrc 不能视为安全沙箱。如果将来需要对不可信网络开放或允许公众注册,应增加独立的编译沙箱和合适的认证/反向代理层。
|
|
386
|
+
|
|
387
|
+
## 许可证和状态说明
|
|
388
|
+
|
|
389
|
+
texLite 使用 GNU Affero General Public License v3.0,详见 [LICENSE](LICENSE)。如果需要闭源修改,或需要不同于 AGPL-3.0 的商业条款,请联系版权所有者获取单独的商业许可证。
|
|
390
|
+
|
|
391
|
+
这是一个早期的单机应用,不是 Overleaf 的直接替代品。请根据实际运行环境审核并调整部署、备份和安全配置。
|
|
@@ -0,0 +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(`
|
|
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
|
+
`]},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,5 @@
|
|
|
1
|
+
import{g as re,u as ne,r as I,j as u}from"./react-CsO_xvbl.js";import{i as z,k as F,M as ie,L as O,T as J,R as Q,S as se,F as ae,l as G}from"./index-Bc5CmFMl.js";const te=z("Clock3",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16.5 12",key:"1aq6pp"}]]);const V=z("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 le=z("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 he=z("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"}]]);var H={exports:{}},K;function oe(){return K||(K=1,(function(C){var o=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},k=-1,A=1,f=0;o.Diff=function(e,r){return[e,r]},o.prototype.diff_main=function(e,r,n,s){typeof s>"u"&&(this.Diff_Timeout<=0?s=Number.MAX_VALUE:s=new Date().getTime()+this.Diff_Timeout*1e3);var a=s;if(e==null||r==null)throw new Error("Null input. (diff_main)");if(e==r)return e?[new o.Diff(f,e)]:[];typeof n>"u"&&(n=!0);var i=n,t=this.diff_commonPrefix(e,r),l=e.substring(0,t);e=e.substring(t),r=r.substring(t),t=this.diff_commonSuffix(e,r);var h=e.substring(e.length-t);e=e.substring(0,e.length-t),r=r.substring(0,r.length-t);var g=this.diff_compute_(e,r,i,a);return l&&g.unshift(new o.Diff(f,l)),h&&g.push(new o.Diff(f,h)),this.diff_cleanupMerge(g),g},o.prototype.diff_compute_=function(e,r,n,s){var a;if(!e)return[new o.Diff(A,r)];if(!r)return[new o.Diff(k,e)];var i=e.length>r.length?e:r,t=e.length>r.length?r:e,l=i.indexOf(t);if(l!=-1)return a=[new o.Diff(A,i.substring(0,l)),new o.Diff(f,t),new o.Diff(A,i.substring(l+t.length))],e.length>r.length&&(a[0][0]=a[2][0]=k),a;if(t.length==1)return[new o.Diff(k,e),new o.Diff(A,r)];var h=this.diff_halfMatch_(e,r);if(h){var g=h[0],c=h[1],v=h[2],m=h[3],b=h[4],p=this.diff_main(g,v,n,s),M=this.diff_main(c,m,n,s);return p.concat([new o.Diff(f,b)],M)}return n&&e.length>100&&r.length>100?this.diff_lineMode_(e,r,s):this.diff_bisect_(e,r,s)},o.prototype.diff_lineMode_=function(e,r,n){var s=this.diff_linesToChars_(e,r);e=s.chars1,r=s.chars2;var a=s.lineArray,i=this.diff_main(e,r,!1,n);this.diff_charsToLines_(i,a),this.diff_cleanupSemantic(i),i.push(new o.Diff(f,""));for(var t=0,l=0,h=0,g="",c="";t<i.length;){switch(i[t][0]){case A:h++,c+=i[t][1];break;case k:l++,g+=i[t][1];break;case f:if(l>=1&&h>=1){i.splice(t-l-h,l+h),t=t-l-h;for(var v=this.diff_main(g,c,!1,n),m=v.length-1;m>=0;m--)i.splice(t,0,v[m]);t=t+v.length}h=0,l=0,g="",c="";break}t++}return i.pop(),i},o.prototype.diff_bisect_=function(e,r,n){for(var s=e.length,a=r.length,i=Math.ceil((s+a)/2),t=i,l=2*i,h=new Array(l),g=new Array(l),c=0;c<l;c++)h[c]=-1,g[c]=-1;h[t+1]=0,g[t+1]=0;for(var v=s-a,m=v%2!=0,b=0,p=0,M=0,E=0,_=0;_<i&&!(new Date().getTime()>n);_++){for(var S=-_+b;S<=_-p;S+=2){var D=t+S,w;S==-_||S!=_&&h[D-1]<h[D+1]?w=h[D+1]:w=h[D-1]+1;for(var N=w-S;w<s&&N<a&&e.charAt(w)==r.charAt(N);)w++,N++;if(h[D]=w,w>s)p+=2;else if(N>a)b+=2;else if(m){var j=t+v-S;if(j>=0&&j<l&&g[j]!=-1){var T=s-g[j];if(w>=T)return this.diff_bisectSplit_(e,r,w,N,n)}}}for(var P=-_+M;P<=_-E;P+=2){var j=t+P,T;P==-_||P!=_&&g[j-1]<g[j+1]?T=g[j+1]:T=g[j-1]+1;for(var B=T-P;T<s&&B<a&&e.charAt(s-T-1)==r.charAt(a-B-1);)T++,B++;if(g[j]=T,T>s)E+=2;else if(B>a)M+=2;else if(!m){var D=t+v-P;if(D>=0&&D<l&&h[D]!=-1){var w=h[D],N=t+w-D;if(T=s-T,w>=T)return this.diff_bisectSplit_(e,r,w,N,n)}}}}return[new o.Diff(k,e),new o.Diff(A,r)]},o.prototype.diff_bisectSplit_=function(e,r,n,s,a){var i=e.substring(0,n),t=r.substring(0,s),l=e.substring(n),h=r.substring(s),g=this.diff_main(i,t,!1,a),c=this.diff_main(l,h,!1,a);return g.concat(c)},o.prototype.diff_linesToChars_=function(e,r){var n=[],s={};n[0]="";function a(h){for(var g="",c=0,v=-1,m=n.length;v<h.length-1;){v=h.indexOf(`
|
|
2
|
+
`,c),v==-1&&(v=h.length-1);var b=h.substring(c,v+1);(s.hasOwnProperty?s.hasOwnProperty(b):s[b]!==void 0)?g+=String.fromCharCode(s[b]):(m==i&&(b=h.substring(c),v=h.length),g+=String.fromCharCode(m),s[b]=m,n[m++]=b),c=v+1}return g}var i=4e4,t=a(e);i=65535;var l=a(r);return{chars1:t,chars2:l,lineArray:n}},o.prototype.diff_charsToLines_=function(e,r){for(var n=0;n<e.length;n++){for(var s=e[n][1],a=[],i=0;i<s.length;i++)a[i]=r[s.charCodeAt(i)];e[n][1]=a.join("")}},o.prototype.diff_commonPrefix=function(e,r){if(!e||!r||e.charAt(0)!=r.charAt(0))return 0;for(var n=0,s=Math.min(e.length,r.length),a=s,i=0;n<a;)e.substring(i,a)==r.substring(i,a)?(n=a,i=n):s=a,a=Math.floor((s-n)/2+n);return a},o.prototype.diff_commonSuffix=function(e,r){if(!e||!r||e.charAt(e.length-1)!=r.charAt(r.length-1))return 0;for(var n=0,s=Math.min(e.length,r.length),a=s,i=0;n<a;)e.substring(e.length-a,e.length-i)==r.substring(r.length-a,r.length-i)?(n=a,i=n):s=a,a=Math.floor((s-n)/2+n);return a},o.prototype.diff_commonOverlap_=function(e,r){var n=e.length,s=r.length;if(n==0||s==0)return 0;n>s?e=e.substring(n-s):n<s&&(r=r.substring(0,n));var a=Math.min(n,s);if(e==r)return a;for(var i=0,t=1;;){var l=e.substring(a-t),h=r.indexOf(l);if(h==-1)return i;t+=h,(h==0||e.substring(a-t)==r.substring(0,t))&&(i=t,t++)}},o.prototype.diff_halfMatch_=function(e,r){if(this.Diff_Timeout<=0)return null;var n=e.length>r.length?e:r,s=e.length>r.length?r:e;if(n.length<4||s.length*2<n.length)return null;var a=this;function i(p,M,E){for(var _=p.substring(E,E+Math.floor(p.length/4)),S=-1,D="",w,N,j,T;(S=M.indexOf(_,S+1))!=-1;){var P=a.diff_commonPrefix(p.substring(E),M.substring(S)),B=a.diff_commonSuffix(p.substring(0,E),M.substring(0,S));D.length<B+P&&(D=M.substring(S-B,S)+M.substring(S,S+P),w=p.substring(0,E-B),N=p.substring(E+P),j=M.substring(0,S-B),T=M.substring(S+P))}return D.length*2>=p.length?[w,N,j,T,D]:null}var t=i(n,s,Math.ceil(n.length/4)),l=i(n,s,Math.ceil(n.length/2)),h;if(!t&&!l)return null;l?t?h=t[4].length>l[4].length?t:l:h=l:h=t;var g,c,v,m;e.length>r.length?(g=h[0],c=h[1],v=h[2],m=h[3]):(v=h[0],m=h[1],g=h[2],c=h[3]);var b=h[4];return[g,c,v,m,b]},o.prototype.diff_cleanupSemantic=function(e){for(var r=!1,n=[],s=0,a=null,i=0,t=0,l=0,h=0,g=0;i<e.length;)e[i][0]==f?(n[s++]=i,t=h,l=g,h=0,g=0,a=e[i][1]):(e[i][0]==A?h+=e[i][1].length:g+=e[i][1].length,a&&a.length<=Math.max(t,l)&&a.length<=Math.max(h,g)&&(e.splice(n[s-1],0,new o.Diff(k,a)),e[n[s-1]+1][0]=A,s--,s--,i=s>0?n[s-1]:-1,t=0,l=0,h=0,g=0,a=null,r=!0)),i++;for(r&&this.diff_cleanupMerge(e),this.diff_cleanupSemanticLossless(e),i=1;i<e.length;){if(e[i-1][0]==k&&e[i][0]==A){var c=e[i-1][1],v=e[i][1],m=this.diff_commonOverlap_(c,v),b=this.diff_commonOverlap_(v,c);m>=b?(m>=c.length/2||m>=v.length/2)&&(e.splice(i,0,new o.Diff(f,v.substring(0,m))),e[i-1][1]=c.substring(0,c.length-m),e[i+1][1]=v.substring(m),i++):(b>=c.length/2||b>=v.length/2)&&(e.splice(i,0,new o.Diff(f,c.substring(0,b))),e[i-1][0]=A,e[i-1][1]=v.substring(0,v.length-b),e[i+1][0]=k,e[i+1][1]=c.substring(b),i++),i++}i++}},o.prototype.diff_cleanupSemanticLossless=function(e){function r(b,p){if(!b||!p)return 6;var M=b.charAt(b.length-1),E=p.charAt(0),_=M.match(o.nonAlphaNumericRegex_),S=E.match(o.nonAlphaNumericRegex_),D=_&&M.match(o.whitespaceRegex_),w=S&&E.match(o.whitespaceRegex_),N=D&&M.match(o.linebreakRegex_),j=w&&E.match(o.linebreakRegex_),T=N&&b.match(o.blanklineEndRegex_),P=j&&p.match(o.blanklineStartRegex_);return T||P?5:N||j?4:_&&!D&&w?3:D||w?2:_||S?1:0}for(var n=1;n<e.length-1;){if(e[n-1][0]==f&&e[n+1][0]==f){var s=e[n-1][1],a=e[n][1],i=e[n+1][1],t=this.diff_commonSuffix(s,a);if(t){var l=a.substring(a.length-t);s=s.substring(0,s.length-t),a=l+a.substring(0,a.length-t),i=l+i}for(var h=s,g=a,c=i,v=r(s,a)+r(a,i);a.charAt(0)===i.charAt(0);){s+=a.charAt(0),a=a.substring(1)+i.charAt(0),i=i.substring(1);var m=r(s,a)+r(a,i);m>=v&&(v=m,h=s,g=a,c=i)}e[n-1][1]!=h&&(h?e[n-1][1]=h:(e.splice(n-1,1),n--),e[n][1]=g,c?e[n+1][1]=c:(e.splice(n+1,1),n--))}n++}},o.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,o.whitespaceRegex_=/\s/,o.linebreakRegex_=/[\r\n]/,o.blanklineEndRegex_=/\n\r?\n$/,o.blanklineStartRegex_=/^\r?\n\r?\n/,o.prototype.diff_cleanupEfficiency=function(e){for(var r=!1,n=[],s=0,a=null,i=0,t=!1,l=!1,h=!1,g=!1;i<e.length;)e[i][0]==f?(e[i][1].length<this.Diff_EditCost&&(h||g)?(n[s++]=i,t=h,l=g,a=e[i][1]):(s=0,a=null),h=g=!1):(e[i][0]==k?g=!0:h=!0,a&&(t&&l&&h&&g||a.length<this.Diff_EditCost/2&&t+l+h+g==3)&&(e.splice(n[s-1],0,new o.Diff(k,a)),e[n[s-1]+1][0]=A,s--,a=null,t&&l?(h=g=!0,s=0):(s--,i=s>0?n[s-1]:-1,h=g=!1),r=!0)),i++;r&&this.diff_cleanupMerge(e)},o.prototype.diff_cleanupMerge=function(e){e.push(new o.Diff(f,""));for(var r=0,n=0,s=0,a="",i="",t;r<e.length;)switch(e[r][0]){case A:s++,i+=e[r][1],r++;break;case k:n++,a+=e[r][1],r++;break;case f:n+s>1?(n!==0&&s!==0&&(t=this.diff_commonPrefix(i,a),t!==0&&(r-n-s>0&&e[r-n-s-1][0]==f?e[r-n-s-1][1]+=i.substring(0,t):(e.splice(0,0,new o.Diff(f,i.substring(0,t))),r++),i=i.substring(t),a=a.substring(t)),t=this.diff_commonSuffix(i,a),t!==0&&(e[r][1]=i.substring(i.length-t)+e[r][1],i=i.substring(0,i.length-t),a=a.substring(0,a.length-t))),r-=n+s,e.splice(r,n+s),a.length&&(e.splice(r,0,new o.Diff(k,a)),r++),i.length&&(e.splice(r,0,new o.Diff(A,i)),r++),r++):r!==0&&e[r-1][0]==f?(e[r-1][1]+=e[r][1],e.splice(r,1)):r++,s=0,n=0,a="",i="";break}e[e.length-1][1]===""&&e.pop();var l=!1;for(r=1;r<e.length-1;)e[r-1][0]==f&&e[r+1][0]==f&&(e[r][1].substring(e[r][1].length-e[r-1][1].length)==e[r-1][1]?(e[r][1]=e[r-1][1]+e[r][1].substring(0,e[r][1].length-e[r-1][1].length),e[r+1][1]=e[r-1][1]+e[r+1][1],e.splice(r-1,1),l=!0):e[r][1].substring(0,e[r+1][1].length)==e[r+1][1]&&(e[r-1][1]+=e[r+1][1],e[r][1]=e[r][1].substring(e[r+1][1].length)+e[r+1][1],e.splice(r+1,1),l=!0)),r++;l&&this.diff_cleanupMerge(e)},o.prototype.diff_xIndex=function(e,r){var n=0,s=0,a=0,i=0,t;for(t=0;t<e.length&&(e[t][0]!==A&&(n+=e[t][1].length),e[t][0]!==k&&(s+=e[t][1].length),!(n>r));t++)a=n,i=s;return e.length!=t&&e[t][0]===k?i:i+(r-a)},o.prototype.diff_prettyHtml=function(e){for(var r=[],n=/&/g,s=/</g,a=/>/g,i=/\n/g,t=0;t<e.length;t++){var l=e[t][0],h=e[t][1],g=h.replace(n,"&").replace(s,"<").replace(a,">").replace(i,"¶<br>");switch(l){case A:r[t]='<ins style="background:#e6ffe6;">'+g+"</ins>";break;case k:r[t]='<del style="background:#ffe6e6;">'+g+"</del>";break;case f:r[t]="<span>"+g+"</span>";break}}return r.join("")},o.prototype.diff_text1=function(e){for(var r=[],n=0;n<e.length;n++)e[n][0]!==A&&(r[n]=e[n][1]);return r.join("")},o.prototype.diff_text2=function(e){for(var r=[],n=0;n<e.length;n++)e[n][0]!==k&&(r[n]=e[n][1]);return r.join("")},o.prototype.diff_levenshtein=function(e){for(var r=0,n=0,s=0,a=0;a<e.length;a++){var i=e[a][0],t=e[a][1];switch(i){case A:n+=t.length;break;case k:s+=t.length;break;case f:r+=Math.max(n,s),n=0,s=0;break}}return r+=Math.max(n,s),r},o.prototype.diff_toDelta=function(e){for(var r=[],n=0;n<e.length;n++)switch(e[n][0]){case A:r[n]="+"+encodeURI(e[n][1]);break;case k:r[n]="-"+e[n][1].length;break;case f:r[n]="="+e[n][1].length;break}return r.join(" ").replace(/%20/g," ")},o.prototype.diff_fromDelta=function(e,r){for(var n=[],s=0,a=0,i=r.split(/\t/g),t=0;t<i.length;t++){var l=i[t].substring(1);switch(i[t].charAt(0)){case"+":try{n[s++]=new o.Diff(A,decodeURI(l))}catch{throw new Error("Illegal escape in diff_fromDelta: "+l)}break;case"-":case"=":var h=parseInt(l,10);if(isNaN(h)||h<0)throw new Error("Invalid number in diff_fromDelta: "+l);var g=e.substring(a,a+=h);i[t].charAt(0)=="="?n[s++]=new o.Diff(f,g):n[s++]=new o.Diff(k,g);break;default:if(i[t])throw new Error("Invalid diff operation in diff_fromDelta: "+i[t])}}if(a!=e.length)throw new Error("Delta length ("+a+") does not equal source text length ("+e.length+").");return n},o.prototype.match_main=function(e,r,n){if(e==null||r==null||n==null)throw new Error("Null input. (match_main)");return n=Math.max(0,Math.min(n,e.length)),e==r?0:e.length?e.substring(n,n+r.length)==r?n:this.match_bitap_(e,r,n):-1},o.prototype.match_bitap_=function(e,r,n){if(r.length>this.Match_MaxBits)throw new Error("Pattern too long for this browser.");var s=this.match_alphabet_(r),a=this;function i(w,N){var j=w/r.length,T=Math.abs(n-N);return a.Match_Distance?j+T/a.Match_Distance:T?1:j}var t=this.Match_Threshold,l=e.indexOf(r,n);l!=-1&&(t=Math.min(i(0,l),t),l=e.lastIndexOf(r,n+r.length),l!=-1&&(t=Math.min(i(0,l),t)));var h=1<<r.length-1;l=-1;for(var g,c,v=r.length+e.length,m,b=0;b<r.length;b++){for(g=0,c=v;g<c;)i(b,n+c)<=t?g=c:v=c,c=Math.floor((v-g)/2+g);v=c;var p=Math.max(1,n-c+1),M=Math.min(n+c,e.length)+r.length,E=Array(M+2);E[M+1]=(1<<b)-1;for(var _=M;_>=p;_--){var S=s[e.charAt(_-1)];if(b===0?E[_]=(E[_+1]<<1|1)&S:E[_]=(E[_+1]<<1|1)&S|((m[_+1]|m[_])<<1|1)|m[_+1],E[_]&h){var D=i(b,_-1);if(D<=t)if(t=D,l=_-1,l>n)p=Math.max(1,2*n-l);else break}}if(i(b+1,n)>t)break;m=E}return l},o.prototype.match_alphabet_=function(e){for(var r={},n=0;n<e.length;n++)r[e.charAt(n)]=0;for(var n=0;n<e.length;n++)r[e.charAt(n)]|=1<<e.length-n-1;return r},o.prototype.patch_addContext_=function(e,r){if(r.length!=0){if(e.start2===null)throw Error("patch not initialized");for(var n=r.substring(e.start2,e.start2+e.length1),s=0;r.indexOf(n)!=r.lastIndexOf(n)&&n.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)s+=this.Patch_Margin,n=r.substring(e.start2-s,e.start2+e.length1+s);s+=this.Patch_Margin;var a=r.substring(e.start2-s,e.start2);a&&e.diffs.unshift(new o.Diff(f,a));var i=r.substring(e.start2+e.length1,e.start2+e.length1+s);i&&e.diffs.push(new o.Diff(f,i)),e.start1-=a.length,e.start2-=a.length,e.length1+=a.length+i.length,e.length2+=a.length+i.length}},o.prototype.patch_make=function(e,r,n){var s,a;if(typeof e=="string"&&typeof r=="string"&&typeof n>"u")s=e,a=this.diff_main(s,r,!0),a.length>2&&(this.diff_cleanupSemantic(a),this.diff_cleanupEfficiency(a));else if(e&&typeof e=="object"&&typeof r>"u"&&typeof n>"u")a=e,s=this.diff_text1(a);else if(typeof e=="string"&&r&&typeof r=="object"&&typeof n>"u")s=e,a=r;else if(typeof e=="string"&&typeof r=="string"&&n&&typeof n=="object")s=e,a=n;else throw new Error("Unknown call format to patch_make.");if(a.length===0)return[];for(var i=[],t=new o.patch_obj,l=0,h=0,g=0,c=s,v=s,m=0;m<a.length;m++){var b=a[m][0],p=a[m][1];switch(!l&&b!==f&&(t.start1=h,t.start2=g),b){case A:t.diffs[l++]=a[m],t.length2+=p.length,v=v.substring(0,g)+p+v.substring(g);break;case k:t.length1+=p.length,t.diffs[l++]=a[m],v=v.substring(0,g)+v.substring(g+p.length);break;case f:p.length<=2*this.Patch_Margin&&l&&a.length!=m+1?(t.diffs[l++]=a[m],t.length1+=p.length,t.length2+=p.length):p.length>=2*this.Patch_Margin&&l&&(this.patch_addContext_(t,c),i.push(t),t=new o.patch_obj,l=0,c=v,h=g);break}b!==A&&(h+=p.length),b!==k&&(g+=p.length)}return l&&(this.patch_addContext_(t,c),i.push(t)),i},o.prototype.patch_deepCopy=function(e){for(var r=[],n=0;n<e.length;n++){var s=e[n],a=new o.patch_obj;a.diffs=[];for(var i=0;i<s.diffs.length;i++)a.diffs[i]=new o.Diff(s.diffs[i][0],s.diffs[i][1]);a.start1=s.start1,a.start2=s.start2,a.length1=s.length1,a.length2=s.length2,r[n]=a}return r},o.prototype.patch_apply=function(e,r){if(e.length==0)return[r,[]];e=this.patch_deepCopy(e);var n=this.patch_addPadding(e);r=n+r+n,this.patch_splitMax(e);for(var s=0,a=[],i=0;i<e.length;i++){var t=e[i].start2+s,l=this.diff_text1(e[i].diffs),h,g=-1;if(l.length>this.Match_MaxBits?(h=this.match_main(r,l.substring(0,this.Match_MaxBits),t),h!=-1&&(g=this.match_main(r,l.substring(l.length-this.Match_MaxBits),t+l.length-this.Match_MaxBits),(g==-1||h>=g)&&(h=-1))):h=this.match_main(r,l,t),h==-1)a[i]=!1,s-=e[i].length2-e[i].length1;else{a[i]=!0,s=h-t;var c;if(g==-1?c=r.substring(h,h+l.length):c=r.substring(h,g+this.Match_MaxBits),l==c)r=r.substring(0,h)+this.diff_text2(e[i].diffs)+r.substring(h+l.length);else{var v=this.diff_main(l,c,!1);if(l.length>this.Match_MaxBits&&this.diff_levenshtein(v)/l.length>this.Patch_DeleteThreshold)a[i]=!1;else{this.diff_cleanupSemanticLossless(v);for(var m=0,b,p=0;p<e[i].diffs.length;p++){var M=e[i].diffs[p];M[0]!==f&&(b=this.diff_xIndex(v,m)),M[0]===A?r=r.substring(0,h+b)+M[1]+r.substring(h+b):M[0]===k&&(r=r.substring(0,h+b)+r.substring(h+this.diff_xIndex(v,m+M[1].length))),M[0]!==k&&(m+=M[1].length)}}}}}return r=r.substring(n.length,r.length-n.length),[r,a]},o.prototype.patch_addPadding=function(e){for(var r=this.Patch_Margin,n="",s=1;s<=r;s++)n+=String.fromCharCode(s);for(var s=0;s<e.length;s++)e[s].start1+=r,e[s].start2+=r;var a=e[0],i=a.diffs;if(i.length==0||i[0][0]!=f)i.unshift(new o.Diff(f,n)),a.start1-=r,a.start2-=r,a.length1+=r,a.length2+=r;else if(r>i[0][1].length){var t=r-i[0][1].length;i[0][1]=n.substring(i[0][1].length)+i[0][1],a.start1-=t,a.start2-=t,a.length1+=t,a.length2+=t}if(a=e[e.length-1],i=a.diffs,i.length==0||i[i.length-1][0]!=f)i.push(new o.Diff(f,n)),a.length1+=r,a.length2+=r;else if(r>i[i.length-1][1].length){var t=r-i[i.length-1][1].length;i[i.length-1][1]+=n.substring(0,t),a.length1+=t,a.length2+=t}return n},o.prototype.patch_splitMax=function(e){for(var r=this.Match_MaxBits,n=0;n<e.length;n++)if(!(e[n].length1<=r)){var s=e[n];e.splice(n--,1);for(var a=s.start1,i=s.start2,t="";s.diffs.length!==0;){var l=new o.patch_obj,h=!0;for(l.start1=a-t.length,l.start2=i-t.length,t!==""&&(l.length1=l.length2=t.length,l.diffs.push(new o.Diff(f,t)));s.diffs.length!==0&&l.length1<r-this.Patch_Margin;){var g=s.diffs[0][0],c=s.diffs[0][1];g===A?(l.length2+=c.length,i+=c.length,l.diffs.push(s.diffs.shift()),h=!1):g===k&&l.diffs.length==1&&l.diffs[0][0]==f&&c.length>2*r?(l.length1+=c.length,a+=c.length,h=!1,l.diffs.push(new o.Diff(g,c)),s.diffs.shift()):(c=c.substring(0,r-l.length1-this.Patch_Margin),l.length1+=c.length,a+=c.length,g===f?(l.length2+=c.length,i+=c.length):h=!1,l.diffs.push(new o.Diff(g,c)),c==s.diffs[0][1]?s.diffs.shift():s.diffs[0][1]=s.diffs[0][1].substring(c.length))}t=this.diff_text2(l.diffs),t=t.substring(t.length-this.Patch_Margin);var v=this.diff_text1(s.diffs).substring(0,this.Patch_Margin);v!==""&&(l.length1+=v.length,l.length2+=v.length,l.diffs.length!==0&&l.diffs[l.diffs.length-1][0]===f?l.diffs[l.diffs.length-1][1]+=v:l.diffs.push(new o.Diff(f,v))),h||e.splice(++n,0,l)}}},o.prototype.patch_toText=function(e){for(var r=[],n=0;n<e.length;n++)r[n]=e[n];return r.join("")},o.prototype.patch_fromText=function(e){var r=[];if(!e)return r;for(var n=e.split(`
|
|
3
|
+
`),s=0,a=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;s<n.length;){var i=n[s].match(a);if(!i)throw new Error("Invalid patch string: "+n[s]);var t=new o.patch_obj;for(r.push(t),t.start1=parseInt(i[1],10),i[2]===""?(t.start1--,t.length1=1):i[2]=="0"?t.length1=0:(t.start1--,t.length1=parseInt(i[2],10)),t.start2=parseInt(i[3],10),i[4]===""?(t.start2--,t.length2=1):i[4]=="0"?t.length2=0:(t.start2--,t.length2=parseInt(i[4],10)),s++;s<n.length;){var l=n[s].charAt(0);try{var h=decodeURI(n[s].substring(1))}catch{throw new Error("Illegal escape in patch_fromText: "+h)}if(l=="-")t.diffs.push(new o.Diff(k,h));else if(l=="+")t.diffs.push(new o.Diff(A,h));else if(l==" ")t.diffs.push(new o.Diff(f,h));else{if(l=="@")break;if(l!=="")throw new Error('Invalid patch mode "'+l+'" in: '+h)}s++}}return r},o.patch_obj=function(){this.diffs=[],this.start1=null,this.start2=null,this.length1=0,this.length2=0},o.patch_obj.prototype.toString=function(){var e,r;this.length1===0?e=this.start1+",0":this.length1==1?e=this.start1+1:e=this.start1+1+","+this.length1,this.length2===0?r=this.start2+",0":this.length2==1?r=this.start2+1:r=this.start2+1+","+this.length2;for(var n=["@@ -"+e+" +"+r+` @@
|
|
4
|
+
`],s,a=0;a<this.diffs.length;a++){switch(this.diffs[a][0]){case A:s="+";break;case k:s="-";break;case f:s=" ";break}n[a+1]=s+encodeURI(this.diffs[a][1])+`
|
|
5
|
+
`}return n.join("").replace(/%20/g," ")},C.exports=o,C.exports.diff_match_patch=o,C.exports.DIFF_DELETE=k,C.exports.DIFF_INSERT=A,C.exports.DIFF_EQUAL=f})(H)),H.exports}var ge=oe();const fe=re(ge);function ve({open:C,project:o,onOpenChange:k,onBeforeMutation:A}){const{t:f,i18n:e}=ne(),[r,n]=I.useState([]),[s,a]=I.useState(null),[i,t]=I.useState(""),[l,h]=I.useState(null),[g,c]=I.useState(""),[v,m]=I.useState(null),[b,p]=I.useState(""),[M,E]=I.useState(null),[_,S]=I.useState(null),[D,w]=I.useState(""),[N,j]=I.useState(""),T=o.permission!=="read",P=o.permission==="owner";I.useEffect(()=>{if(!C)return;const d=new AbortController;return w("load"),j(""),m(null),F(`/api/projects/${o.id}/history`,{signal:d.signal}).then(y=>{n(y.versions),a(y.stats),t(L=>y.versions.some(R=>R.id===L)?L:y.versions[0]?.id??"")}).catch(y=>{X(y)||j($(y))}).finally(()=>{d.signal.aborted||w("")}),()=>d.abort()},[C,o.id]),I.useEffect(()=>{if(!C||!i){h(null);return}const d=new AbortController;return w("detail"),m(null),F(`/api/projects/${o.id}/history/${i}`,{signal:d.signal}).then(y=>{h(y),p(y.version.label??"");const L=y.version.changedPaths.find(R=>y.files.some(U=>U.path===R))??y.files[0]?.path??"";c(L)}).catch(y=>{X(y)||j($(y))}).finally(()=>{d.signal.aborted||w("")}),()=>d.abort()},[C,o.id,i]);const B=async(d=g)=>{if(d){w("compare"),j("");try{m(await F(`/api/projects/${o.id}/history/${i}/file?path=${encodeURIComponent(d)}`))}catch(y){j($(y))}finally{w("")}}},Z=async()=>{if(l){w("label"),j("");try{const d=await F(`/api/projects/${o.id}/history/${l.version.id}`,{method:"PATCH",body:JSON.stringify({label:b.trim()||null})});h(y=>y&&{...y,version:d.version}),n(y=>y.map(L=>L.id===d.version.id?d.version:L)),a(d.stats)}catch(d){j($(d))}finally{w("")}}},Y=async()=>{const d=_;if(d){S(null),w("delete"),j("");try{if(!await A())throw new Error(f("errors.collaborationUnavailable"));const y=d==="all"?`/api/projects/${o.id}/history`:`/api/projects/${o.id}/history/${d.id}`,L=await F(y,{method:"DELETE"});if(a(L.stats),d==="all")n([]),t(""),h(null),m(null);else{const R=r.filter(U=>U.id!==d.id);n(R),i===d.id&&(t(R[0]?.id??""),h(null),m(null))}}catch(y){j($(y))}finally{w("")}}},q=async()=>{const d=M;if(!(!d||!l)){E(null),w("restore"),j("");try{if(!await A())throw new Error(f("errors.collaborationUnavailable"));await F(`/api/projects/${o.id}/history/${l.version.id}/restore`,{method:"POST",body:JSON.stringify(d==="project"?{}:{path:d})}),window.location.reload()}catch(y){j($(y)),w("")}}},x=I.useMemo(()=>{if(!v)return[];const d=new fe;d.Diff_Timeout=1;const y=d.diff_main(v.historical,v.comparison,!0);return d.diff_cleanupSemantic(y),y},[v]),ee=_&&_!=="all"?_.label||new Date(_.createdAt).toLocaleString(e.resolvedLanguage):"";return u.jsxs(u.Fragment,{children:[u.jsx(ie,{open:C,extraWide:!0,className:"history-dialog-modal",title:f("history.title"),description:f("history.description"),onOpenChange:k,footer:u.jsx("button",{onClick:()=>k(!1),children:f("common.close")}),children:u.jsxs("div",{className:"history-dialog",children:[N&&u.jsx("p",{className:"error history-message",children:N}),D==="load"&&r.length===0?u.jsxs("div",{className:"history-loading",children:[u.jsx(O,{className:"spin",size:22}),f("common.loading")]}):u.jsxs(u.Fragment,{children:[u.jsxs("aside",{className:"history-timeline",children:[P&&s&&u.jsxs("div",{className:`history-stats${s.storageLimitExceeded?" exceeded":""}`,children:[u.jsxs("header",{children:[u.jsxs("span",{children:[u.jsx(le,{size:14}),f("history.storage")]}),u.jsx("button",{type:"button",disabled:!!D||r.length===0,title:f("history.clearAll"),"aria-label":f("history.clearAll"),onClick:()=>S("all"),children:u.jsx(J,{size:13})})]}),u.jsx("strong",{children:f("history.storageUsage",{used:W(s.objectBytes),limit:W(s.maxStorageBytes)})}),u.jsx("progress",{max:s.maxStorageBytes,value:Math.min(s.objectBytes,s.maxStorageBytes)}),u.jsxs("small",{children:[f("history.versionUsage",{count:s.ordinaryVersionCount,limit:s.maxVersions}),s.labeledVersionCount>0?` · ${f("history.protectedVersions",{count:s.labeledVersionCount})}`:""]}),s.storageLimitExceeded&&u.jsx("small",{className:"history-storage-warning",children:f("history.storageExceeded")})]}),r.map(d=>u.jsxs("button",{className:d.id===i?"active":"",onClick:()=>t(d.id),children:[u.jsx(V,{size:15}),u.jsxs("span",{children:[u.jsx("strong",{children:d.label||f(`history.reasons.${d.reason}`)}),u.jsx("small",{children:new Date(d.createdAt).toLocaleString(e.resolvedLanguage)}),u.jsxs("small",{children:[d.author?.name??f("editor.deletedUser")," · ",f("history.changedCount",{count:d.changedPaths.length})]})]})]},d.id)),r.length===0&&u.jsx("p",{className:"muted padded",children:f("history.empty")})]}),u.jsx("section",{className:"history-detail",children:l&&u.jsxs(u.Fragment,{children:[u.jsxs("header",{className:"history-detail-header",children:[u.jsxs("div",{children:[u.jsx("strong",{children:l.version.label||f(`history.reasons.${l.version.reason}`)}),u.jsxs("small",{children:[u.jsx(te,{size:12}),new Date(l.version.createdAt).toLocaleString(e.resolvedLanguage)," · ",f("history.fileCount",{count:l.version.fileCount})]})]}),u.jsxs("span",{className:"history-detail-actions",children:[P&&u.jsxs("button",{className:"danger-text",disabled:!!D,onClick:()=>S(l.version),children:[u.jsx(J,{size:14}),f("history.deleteVersion")]}),T&&u.jsxs("button",{className:"danger-text",disabled:!!D,onClick:()=>E("project"),children:[u.jsx(Q,{size:14}),f("history.restoreProject")]})]})]}),T&&u.jsxs("div",{className:"history-label",children:[u.jsxs("label",{children:[u.jsx(he,{size:14}),u.jsx("input",{value:b,maxLength:80,placeholder:f("history.labelPlaceholder"),onChange:d=>p(d.target.value)})]}),u.jsxs("button",{disabled:D==="label"||b.trim()===(l.version.label??""),onClick:()=>{Z()},children:[D==="label"?u.jsx(O,{className:"spin",size:13}):u.jsx(se,{size:13}),f("history.saveLabel")]})]}),u.jsxs("div",{className:"history-files",children:[u.jsx("div",{className:"history-file-list",children:l.files.map(d=>{const y=l.version.changedPaths.includes(d.path);return u.jsxs("button",{className:d.path===g?"active":"",title:d.path,onClick:()=>{c(d.path),m(null)},children:[u.jsx(ae,{size:13}),u.jsx("span",{children:d.path}),y&&u.jsx("small",{children:f("history.changed")})]},d.path)})}),u.jsxs("div",{className:"history-file-actions",children:[u.jsxs("button",{disabled:!g||!!D,onClick:()=>{B()},children:[D==="compare"?u.jsx(O,{className:"spin",size:13}):u.jsx(V,{size:13}),f("history.compareCurrent")]}),T&&u.jsxs("button",{disabled:!g||!!D,onClick:()=>E(g),children:[u.jsx(Q,{size:13}),f("history.restoreFile")]})]})]}),u.jsx("div",{className:"history-comparison",children:v?u.jsxs(u.Fragment,{children:[u.jsxs("header",{children:[u.jsx("strong",{children:v.path}),u.jsx("small",{children:f("history.comparingCurrent")})]}),u.jsx("pre",{children:x.map(([d,y],L)=>u.jsx("span",{className:d<0?"history-removed":d>0?"history-added":"",children:y},L))})]}):u.jsxs("div",{className:"history-comparison-empty",children:[u.jsx(V,{size:25}),u.jsx("span",{children:f("history.chooseFile")})]})})]})})]})]})}),u.jsx(G,{open:!!M,title:f(M==="project"?"history.restoreProjectTitle":"history.restoreFileTitle"),description:M==="project"?f("history.restoreProjectDescription"):f("history.restoreFileDescription",{path:M??""}),confirmLabel:f("history.restore"),danger:!0,onCancel:()=>E(null),onConfirm:()=>{q()}}),u.jsx(G,{open:!!_,title:f(_==="all"?"history.clearAllTitle":"history.deleteVersionTitle"),description:_==="all"?f("history.clearAllDescription"):f("history.deleteVersionDescription",{name:ee}),confirmLabel:f(_==="all"?"history.clearAll":"history.deleteVersion"),danger:!0,onCancel:()=>S(null),onConfirm:()=>{Y()}})]})}function W(C){return C<1024?`${C} B`:C<1024*1024?`${Math.round(C/102.4)/10} KB`:C<1024*1024*1024?`${Math.round(C/(1024*1024)*10)/10} MB`:`${Math.round(C/(1024*1024*1024)*10)/10} GB`}function $(C){return C instanceof Error?C.message:String(C)}function X(C){return C instanceof Error&&C.name==="AbortError"}export{ve as HistoryDialog};
|