texlite 0.9.0 → 0.9.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/dist/client/assets/{AdminUsers-3CHb5012.js → AdminUsers-Dazar9cj.js} +1 -1
- package/dist/client/assets/{CitationLibraryDialog-zPpsUhWJ.js → CitationLibraryDialog-VSUiSPgG.js} +1 -1
- package/dist/client/assets/{Dashboard-CWfSLzD3.js → Dashboard-CoE5ESfb.js} +2 -2
- package/dist/client/assets/GitDialog-C9Loz4Al.js +3 -0
- package/dist/client/assets/HistoryDialog-3UNN_jhU.js +7 -0
- package/dist/client/assets/{LatexEditor-CM445Tk0.js → LatexEditor-Bq76v8sO.js} +2 -2
- package/dist/client/assets/{PdfPreview-BFVNczSk.js → PdfPreview-BKhcS-2c.js} +1 -1
- package/dist/client/assets/{ProjectIconPickerDialog-Cf0zBf8u.js → ProjectIconPickerDialog-BoWMjQVm.js} +1 -1
- package/dist/client/assets/{ProjectNavigationDialogs-TPbe11iR.js → ProjectNavigationDialogs-CZKTnxrl.js} +1 -1
- package/dist/client/assets/{ProjectSettings-CDQvIlQ4.js → ProjectSettings-DiOQVJ40.js} +1 -1
- package/dist/client/assets/ProjectWorkspace-DqLwkGpT.js +27 -0
- package/dist/client/assets/SelectionHistoryDialog-D3dO83lg.js +3 -0
- package/dist/client/assets/{SystemMetricsDialog-CuqHPUnn.js → SystemMetricsDialog-0zCwLtH_.js} +1 -1
- package/dist/client/assets/{TagManagementDialog-gRLM3lLx.js → TagManagementDialog-C6KDwtvd.js} +1 -1
- package/dist/client/assets/{WordCountDialog-B4PI0vEk.js → WordCountDialog-TwpyAP3m.js} +1 -1
- package/dist/client/assets/check-sZxOOKtS.js +1 -0
- package/dist/client/assets/chevron-left-32SrOwa5.js +1 -0
- package/dist/client/assets/{hard-drive-6uYFyezl.js → hard-drive-XHSiS_A7.js} +1 -1
- package/dist/client/assets/index-BRr3Kuxh.css +1 -0
- package/dist/client/assets/{index-C0t11tlT.js → index-BjzELSQ-.js} +3 -5
- package/dist/client/assets/{index-BRT5O-Hg.js → index-Cd6-F29b.js} +1 -1
- package/dist/client/assets/{key-round-DOAxDSfz.js → key-round-C-CXCWnz.js} +1 -1
- package/dist/client/assets/minimize-2-BMDPVaF-.js +1 -0
- package/dist/client/assets/{minus-9eVJnugf.js → minus-BIa7-KuM.js} +1 -1
- package/dist/client/assets/{plus-BGXipRoG.js → plus-BKDWy4Ui.js} +1 -1
- package/dist/client/assets/{refresh-cw-Dvp3O90f.js → refresh-cw-Bo7-a7-I.js} +1 -1
- package/dist/client/assets/{search-b1ORp1k8.js → search-Bz9Ob5mT.js} +1 -1
- package/dist/client/assets/{sigma-Bk0_MzWi.js → sigma-DxlHf9gv.js} +1 -1
- package/dist/client/assets/spellCheck-BfZEnpwY.js +1 -0
- package/dist/client/assets/{tags-JAkbmWns.js → tags-IMZehjzE.js} +1 -1
- package/dist/client/assets/{type-CE2DYkPH.js → type-DTWpFQNQ.js} +1 -1
- package/dist/client/assets/{user-plus-CKalKSsw.js → user-plus--aVpd8K6.js} +1 -1
- package/dist/client/assets/{x-Bq29njHL.js → x-Tbo3aA0J.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/harper.js +205 -8
- package/dist/server/history.js +53 -3
- package/dist/server/i18n.js +8 -0
- package/dist/server/latexSpellMask.js +197 -43
- package/dist/server/routes/comments.js +13 -1
- package/dist/server/routes/projectHistory.js +13 -1
- package/dist/server/routes/projects.js +36 -4
- package/package.json +1 -1
- package/dist/client/assets/GitDialog-BUU2U_LV.js +0 -3
- package/dist/client/assets/HistoryDialog-B6aopsrw.js +0 -7
- package/dist/client/assets/ProjectWorkspace-C6GrvKN6.js +0 -27
- package/dist/client/assets/SelectionHistoryDialog-CyE0_YDs.js +0 -3
- package/dist/client/assets/check-BXvzrdoz.js +0 -1
- package/dist/client/assets/chevron-left-BB5MYb7g.js +0 -1
- package/dist/client/assets/index-DXWp2bzM.css +0 -1
- package/dist/client/assets/minimize-2-BWtbag2I.js +0 -1
- package/dist/client/assets/spellCheck-BJjWgnef.js +0 -1
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
// This intentionally recognizes only the parts of LaTeX that are clearly not
|
|
2
2
|
// prose. It is a single-pass lexer, not a parser: malformed delimiters remain
|
|
3
3
|
// narrow, so they never hide the rest of a document or cause backtracking.
|
|
4
|
+
const literalEnvironments = new Set([
|
|
5
|
+
"verbatim", "verbatim*", "bverbatim", "bverbatim*", "lverbatim", "lverbatim*", "saveverbatim", "saveverbatim*", "verbatimout",
|
|
6
|
+
"lstlisting", "minted", "minted*", "filecontents", "filecontents*", "luacode", "luacodestar", "comment"
|
|
7
|
+
]);
|
|
4
8
|
const nonProseEnvironments = new Set([
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
+
// Display and helper math environments commonly used by amsmath/mathtools.
|
|
10
|
+
"math", "displaymath", "equation", "equation*", "align", "align*", "alignat", "alignat*", "aligned", "alignedat", "gather", "gather*", "gathered", "multline", "multline*", "multlined", "flalign", "flalign*", "split", "cases", "dcases", "rcases", "drcases",
|
|
11
|
+
"array", "matrix", "pmatrix", "bmatrix", "vmatrix", "smallmatrix", "psmallmatrix", "bsmallmatrix", "vsmallmatrix",
|
|
12
|
+
// Diagram, table, and literal/code environments. Keep prose containers such
|
|
13
|
+
// as figure, table, theorem, and caption outside this list.
|
|
14
|
+
"tikzpicture", "tikzcd", "axis", "scope", "pgfonlayer", "pgfpicture",
|
|
15
|
+
"tabular", "tabularx", "tabulary", "longtable",
|
|
16
|
+
"verbatim", "verbatim*", "bverbatim", "bverbatim*", "lverbatim", "lverbatim*", "saveverbatim", "saveverbatim*", "verbatimout",
|
|
17
|
+
"lstlisting", "minted", "minted*", "filecontents", "filecontents*", "luacode", "luacodestar", "comment", "algorithmic"
|
|
9
18
|
]);
|
|
10
19
|
const opaqueArgumentCommands = new Set([
|
|
11
20
|
"documentclass", "usepackage", "requirepackage", "includegraphics", "tikzset", "pgfplotsset", "hypersetup", "lstset",
|
|
12
21
|
"label", "hypertarget", "ref", "pageref", "autoref", "nameref", "hyperref", "index", "gls", "glspl",
|
|
13
|
-
"bibliography", "addbibresource", "input", "include", "url", "path", "setlength", "setcounter", "color", "textcolor", "colorbox", "pagecolor"
|
|
22
|
+
"bibliography", "addbibresource", "input", "include", "url", "path", "setlength", "setcounter", "color", "textcolor", "colorbox", "pagecolor",
|
|
23
|
+
// These arguments are code, identifiers, or paths rather than prose.
|
|
24
|
+
"texttt", "detokenize", "lstinputlisting"
|
|
25
|
+
]);
|
|
26
|
+
/** Number of brace arguments after a macro/environment target. */
|
|
27
|
+
const macroDefinitionArgumentCounts = new Map([
|
|
28
|
+
["newcommand", 1], ["renewcommand", 1], ["providecommand", 1], ["declarerobustcommand", 1],
|
|
29
|
+
["newenvironment", 2], ["renewenvironment", 2],
|
|
30
|
+
["newdocumentcommand", 2], ["renewdocumentcommand", 2], ["providedocumentcommand", 2], ["declaredocumentcommand", 2],
|
|
31
|
+
["declaremathoperator", 1], ["declarepaireddelimiter", 2]
|
|
14
32
|
]);
|
|
15
33
|
function addRange(ranges, from, to) {
|
|
16
34
|
if (to > from)
|
|
@@ -26,7 +44,7 @@ function isCommandNameCharacter(character) {
|
|
|
26
44
|
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57)
|
|
27
45
|
|| character === "@" || character === ":" || character === "_";
|
|
28
46
|
}
|
|
29
|
-
function balancedArgument(source, start, open, close) {
|
|
47
|
+
function balancedArgument(source, start, open, close, literal = false) {
|
|
30
48
|
if (source[start] !== open)
|
|
31
49
|
return null;
|
|
32
50
|
let depth = 1;
|
|
@@ -35,6 +53,13 @@ function balancedArgument(source, start, open, close) {
|
|
|
35
53
|
index += 1;
|
|
36
54
|
continue;
|
|
37
55
|
}
|
|
56
|
+
if (!literal && source[index] === "%") {
|
|
57
|
+
const lineEnd = source.indexOf("\n", index);
|
|
58
|
+
if (lineEnd < 0)
|
|
59
|
+
return null;
|
|
60
|
+
index = lineEnd;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
38
63
|
if (source[index] === open)
|
|
39
64
|
depth += 1;
|
|
40
65
|
else if (source[index] === close && --depth === 0)
|
|
@@ -48,6 +73,17 @@ function skipWhitespace(source, start) {
|
|
|
48
73
|
index += 1;
|
|
49
74
|
return index;
|
|
50
75
|
}
|
|
76
|
+
/** Skip whitespace and TeX comments between arguments while masking comments. */
|
|
77
|
+
function skipTrivia(source, start, ranges) {
|
|
78
|
+
let index = skipWhitespace(source, start);
|
|
79
|
+
while (source[index] === "%") {
|
|
80
|
+
const lineEnd = source.indexOf("\n", index);
|
|
81
|
+
const end = lineEnd < 0 ? source.length : lineEnd;
|
|
82
|
+
addRange(ranges, index, end);
|
|
83
|
+
index = skipWhitespace(source, lineEnd < 0 ? source.length : lineEnd + 1);
|
|
84
|
+
}
|
|
85
|
+
return index;
|
|
86
|
+
}
|
|
51
87
|
function readCommand(source, start) {
|
|
52
88
|
let end = start + 1;
|
|
53
89
|
if (isCommandNameCharacter(source[end]))
|
|
@@ -64,23 +100,103 @@ function isCitationCommand(name) {
|
|
|
64
100
|
const normalized = name.toLocaleLowerCase("en-US");
|
|
65
101
|
return normalized === "cite" || normalized.startsWith("cite") || normalized.endsWith("cite");
|
|
66
102
|
}
|
|
67
|
-
function
|
|
103
|
+
function opaqueArgumentCount(name) {
|
|
68
104
|
const normalized = name.toLocaleLowerCase("en-US");
|
|
69
|
-
|
|
105
|
+
if (normalized === "definecolor" || normalized === "colorlet")
|
|
106
|
+
return 3;
|
|
107
|
+
if (normalized === "mint" || normalized === "inputminted")
|
|
108
|
+
return 2;
|
|
109
|
+
return opaqueArgumentCommands.has(normalized) || isCitationCommand(normalized) || normalized === "href" ? 1 : 0;
|
|
70
110
|
}
|
|
71
|
-
function
|
|
111
|
+
function maskBraceArguments(source, start, count, ranges) {
|
|
72
112
|
let cursor = start;
|
|
73
|
-
const normalized = name.toLocaleLowerCase("en-US");
|
|
74
|
-
const count = normalized === "definecolor" || normalized === "colorlet" ? 3 : 1;
|
|
75
113
|
for (let index = 0; index < count; index += 1) {
|
|
76
114
|
const argument = balancedArgument(source, cursor, "{", "}");
|
|
77
115
|
if (!argument)
|
|
78
116
|
break;
|
|
79
117
|
addRange(ranges, argument.from, argument.to);
|
|
80
|
-
cursor =
|
|
118
|
+
cursor = skipTrivia(source, argument.to, ranges);
|
|
81
119
|
}
|
|
82
120
|
return cursor;
|
|
83
121
|
}
|
|
122
|
+
function maskMacroTarget(source, start, ranges) {
|
|
123
|
+
const bracedTarget = balancedArgument(source, start, "{", "}");
|
|
124
|
+
if (bracedTarget) {
|
|
125
|
+
addRange(ranges, bracedTarget.from, bracedTarget.to);
|
|
126
|
+
return skipTrivia(source, bracedTarget.to, ranges);
|
|
127
|
+
}
|
|
128
|
+
if (source[start] === "\\") {
|
|
129
|
+
const target = readCommand(source, start);
|
|
130
|
+
addRange(ranges, start, target.end);
|
|
131
|
+
return skipTrivia(source, target.end, ranges);
|
|
132
|
+
}
|
|
133
|
+
return start;
|
|
134
|
+
}
|
|
135
|
+
function maskMacroDefinition(source, start, definitionArgumentCount, ranges) {
|
|
136
|
+
let cursor = maskMacroTarget(source, start, ranges);
|
|
137
|
+
if (cursor === start)
|
|
138
|
+
return cursor;
|
|
139
|
+
// newcommand-style macros may have both an argument-count option and a
|
|
140
|
+
// default-argument option. Mask every immediately following option before
|
|
141
|
+
// their definition bodies, while keeping malformed input narrow.
|
|
142
|
+
for (;;) {
|
|
143
|
+
const option = balancedArgument(source, cursor, "[", "]");
|
|
144
|
+
if (!option)
|
|
145
|
+
break;
|
|
146
|
+
addRange(ranges, option.from, option.to);
|
|
147
|
+
cursor = skipTrivia(source, option.to, ranges);
|
|
148
|
+
}
|
|
149
|
+
return maskBraceArguments(source, cursor, definitionArgumentCount, ranges);
|
|
150
|
+
}
|
|
151
|
+
function maskDelimitedCode(source, start, ranges) {
|
|
152
|
+
const delimiter = source[start];
|
|
153
|
+
if (!delimiter || isWhitespace(delimiter))
|
|
154
|
+
return start;
|
|
155
|
+
// TeX's delimiter-based inline literal commands cannot span a line. If a
|
|
156
|
+
// user is still typing the closing delimiter, hide only the incomplete
|
|
157
|
+
// current line rather than accidentally suppressing all later prose until a
|
|
158
|
+
// matching character happens to occur.
|
|
159
|
+
for (let cursor = start + 1; cursor < source.length; cursor += 1) {
|
|
160
|
+
const character = source[cursor];
|
|
161
|
+
if (character === delimiter) {
|
|
162
|
+
addRange(ranges, start, cursor + 1);
|
|
163
|
+
return skipTrivia(source, cursor + 1, ranges);
|
|
164
|
+
}
|
|
165
|
+
if (character === "\r" || character === "\n") {
|
|
166
|
+
addRange(ranges, start, cursor);
|
|
167
|
+
return cursor;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
addRange(ranges, start, source.length);
|
|
171
|
+
return source.length;
|
|
172
|
+
}
|
|
173
|
+
function maskDelimitedOrBracedCode(source, start, ranges) {
|
|
174
|
+
if (source[start] !== "{")
|
|
175
|
+
return maskDelimitedCode(source, start, ranges);
|
|
176
|
+
// The brace form is also inline literal code. Preserve nested braces and
|
|
177
|
+
// escaped characters, but never let an unfinished argument conceal a later
|
|
178
|
+
// source line from the writing checker.
|
|
179
|
+
let depth = 1;
|
|
180
|
+
for (let cursor = start + 1; cursor < source.length; cursor += 1) {
|
|
181
|
+
const character = source[cursor];
|
|
182
|
+
if (character === "\\" && source[cursor + 1] !== "\r" && source[cursor + 1] !== "\n") {
|
|
183
|
+
cursor += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (character === "\r" || character === "\n") {
|
|
187
|
+
addRange(ranges, start, cursor);
|
|
188
|
+
return cursor;
|
|
189
|
+
}
|
|
190
|
+
if (character === "{")
|
|
191
|
+
depth += 1;
|
|
192
|
+
else if (character === "}" && --depth === 0) {
|
|
193
|
+
addRange(ranges, start, cursor + 1);
|
|
194
|
+
return cursor + 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
addRange(ranges, start, source.length);
|
|
198
|
+
return source.length;
|
|
199
|
+
}
|
|
84
200
|
function rawUrlEnd(source, start) {
|
|
85
201
|
const first = source[start];
|
|
86
202
|
if (first !== "h" && first !== "H" && first !== "f" && first !== "F")
|
|
@@ -115,6 +231,33 @@ export function ignoredLatexRanges(source) {
|
|
|
115
231
|
// The same technique keeps unmatched $ / \[ delimiters cheap and local.
|
|
116
232
|
let pendingMath = null;
|
|
117
233
|
for (let index = 0; index < source.length;) {
|
|
234
|
+
// An open literal environment owns its full body. Do not parse
|
|
235
|
+
// dollars, comments, or nested commands within it: literal code often
|
|
236
|
+
// contains all of those. Only look for its matching \end marker.
|
|
237
|
+
const activeNonProse = nonProseStack.at(-1);
|
|
238
|
+
if (activeNonProse && literalEnvironments.has(activeNonProse.name)) {
|
|
239
|
+
if (source[index] === "\\") {
|
|
240
|
+
const command = readCommand(source, index);
|
|
241
|
+
if (command.name === "end") {
|
|
242
|
+
const cursor = skipTrivia(source, command.end, ranges);
|
|
243
|
+
const environment = balancedArgument(source, cursor, "{", "}");
|
|
244
|
+
const environmentName = environment
|
|
245
|
+
? source.slice(environment.from + 1, environment.to - 1).trim().toLocaleLowerCase("en-US")
|
|
246
|
+
: "";
|
|
247
|
+
if (environment && environmentName === activeNonProse.name) {
|
|
248
|
+
activeNonProse.range.to = skipTrivia(source, environment.to, ranges);
|
|
249
|
+
nonProseStack.pop();
|
|
250
|
+
index = activeNonProse.range.to;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
index = Math.max(index + 1, command.end);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
index += 1;
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
118
261
|
if (pendingMath?.close.startsWith("\\") && source.startsWith(pendingMath.close, index)) {
|
|
119
262
|
pendingMath.range.to = index + pendingMath.close.length;
|
|
120
263
|
index += pendingMath.close.length;
|
|
@@ -171,44 +314,41 @@ export function ignoredLatexRanges(source) {
|
|
|
171
314
|
continue;
|
|
172
315
|
}
|
|
173
316
|
const command = readCommand(source, commandStart);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
317
|
+
const commandName = command.name.toLocaleLowerCase("en-US");
|
|
318
|
+
// Reserve the command span before scanning trivia, which may append spans.
|
|
319
|
+
const commandRange = { from: commandStart, to: command.end };
|
|
320
|
+
ranges.push(commandRange);
|
|
321
|
+
let cursor = commandName === "verb" || commandName === "lstinline"
|
|
322
|
+
? command.end : skipTrivia(source, command.end, ranges);
|
|
323
|
+
if (commandName === "verb") {
|
|
324
|
+
const options = command.name === "Verb" ? balancedArgument(source, cursor, "[", "]") : null;
|
|
325
|
+
if (options) {
|
|
326
|
+
addRange(ranges, options.from, options.to);
|
|
327
|
+
cursor = skipWhitespace(source, options.to);
|
|
184
328
|
}
|
|
329
|
+
cursor = maskDelimitedOrBracedCode(source, cursor, ranges);
|
|
185
330
|
index = Math.max(command.end, cursor);
|
|
186
331
|
continue;
|
|
187
332
|
}
|
|
188
333
|
if (command.name === "begin" || command.name === "end") {
|
|
189
334
|
const environment = balancedArgument(source, cursor, "{", "}");
|
|
190
335
|
if (!environment) {
|
|
191
|
-
addRange(ranges, commandStart, command.end);
|
|
192
336
|
index = command.end;
|
|
193
337
|
continue;
|
|
194
338
|
}
|
|
195
339
|
const environmentName = source.slice(environment.from + 1, environment.to - 1).trim().toLocaleLowerCase("en-US");
|
|
196
|
-
|
|
340
|
+
addRange(ranges, environment.from, environment.to);
|
|
341
|
+
cursor = skipTrivia(source, environment.to, ranges);
|
|
197
342
|
let options = null;
|
|
198
343
|
if (command.name === "begin") {
|
|
199
344
|
options = balancedArgument(source, cursor, "[", "]");
|
|
200
|
-
if (options)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const range = { from: commandStart, to: cursor };
|
|
204
|
-
ranges.push(range);
|
|
205
|
-
nonProseStack.push({ name: environmentName, range });
|
|
345
|
+
if (options) {
|
|
346
|
+
addRange(ranges, options.from, options.to);
|
|
347
|
+
cursor = skipTrivia(source, options.to, ranges);
|
|
206
348
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (options)
|
|
211
|
-
addRange(ranges, options.from, options.to);
|
|
349
|
+
if (nonProseEnvironments.has(environmentName)) {
|
|
350
|
+
commandRange.to = cursor;
|
|
351
|
+
nonProseStack.push({ name: environmentName, range: commandRange });
|
|
212
352
|
}
|
|
213
353
|
}
|
|
214
354
|
else {
|
|
@@ -217,22 +357,36 @@ export function ignoredLatexRanges(source) {
|
|
|
217
357
|
nonProseStack.pop();
|
|
218
358
|
open.range.to = cursor;
|
|
219
359
|
}
|
|
220
|
-
else {
|
|
221
|
-
addRange(ranges, commandStart, command.end);
|
|
222
|
-
addRange(ranges, environment.from, environment.to);
|
|
223
|
-
}
|
|
224
360
|
}
|
|
225
361
|
index = Math.max(command.end, cursor);
|
|
226
362
|
continue;
|
|
227
363
|
}
|
|
228
|
-
addRange(ranges, commandStart, command.end);
|
|
229
364
|
const options = balancedArgument(source, cursor, "[", "]");
|
|
230
365
|
if (options) {
|
|
231
366
|
addRange(ranges, options.from, options.to);
|
|
232
|
-
cursor = skipWhitespace(source, options.to);
|
|
367
|
+
cursor = commandName === "lstinline" ? skipWhitespace(source, options.to) : skipTrivia(source, options.to, ranges);
|
|
368
|
+
}
|
|
369
|
+
if (commandName === "lstinline") {
|
|
370
|
+
cursor = maskDelimitedOrBracedCode(source, cursor, ranges);
|
|
371
|
+
}
|
|
372
|
+
else if (commandName === "mintinline") {
|
|
373
|
+
const language = balancedArgument(source, cursor, "{", "}");
|
|
374
|
+
if (language) {
|
|
375
|
+
addRange(ranges, language.from, language.to);
|
|
376
|
+
cursor = maskDelimitedOrBracedCode(source, skipWhitespace(source, language.to), ranges);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const macroDefinitionArgumentCount = macroDefinitionArgumentCounts.get(commandName);
|
|
381
|
+
if (macroDefinitionArgumentCount !== undefined) {
|
|
382
|
+
cursor = maskMacroDefinition(source, cursor, macroDefinitionArgumentCount, ranges);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
const opaqueCount = opaqueArgumentCount(commandName);
|
|
386
|
+
if (opaqueCount > 0)
|
|
387
|
+
cursor = maskBraceArguments(source, cursor, opaqueCount, ranges);
|
|
388
|
+
}
|
|
233
389
|
}
|
|
234
|
-
if (masksOpaqueArguments(command.name))
|
|
235
|
-
cursor = maskOpaqueArguments(source, cursor, command.name, ranges);
|
|
236
390
|
index = Math.max(command.end, cursor);
|
|
237
391
|
}
|
|
238
392
|
return mergeOrderedRanges(ranges);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { requireUser } from "../auth.js";
|
|
4
4
|
import { createSourceAnchor, offsetToLine } from "../anchors.js";
|
|
5
5
|
import { resolveSourcePath, safeRelativePath } from "../files.js";
|
|
@@ -106,6 +106,18 @@ export function registerCommentRoutes(app, context) {
|
|
|
106
106
|
if (!Number.isInteger(body.startOffset) || !Number.isInteger(body.endOffset)) {
|
|
107
107
|
return apiError(reply, 400, "COMMENT_RANGE_INVALID");
|
|
108
108
|
}
|
|
109
|
+
// The browser supplies the exact source revision the user selected.
|
|
110
|
+
// Keep this optional for an already-open client during an upgrade;
|
|
111
|
+
// current clients never bind an annotation to offsets from a different
|
|
112
|
+
// revision.
|
|
113
|
+
if (body.sourceHash !== undefined) {
|
|
114
|
+
if (typeof body.sourceHash !== "string" || !/^[a-f0-9]{64}$/.test(body.sourceHash)) {
|
|
115
|
+
return apiError(reply, 400, "REQUEST_INVALID");
|
|
116
|
+
}
|
|
117
|
+
const currentHash = createHash("sha256").update(source, "utf8").digest("hex");
|
|
118
|
+
if (currentHash !== body.sourceHash)
|
|
119
|
+
return apiError(reply, 409, "COMMENT_SOURCE_CHANGED");
|
|
120
|
+
}
|
|
109
121
|
const anchor = createSourceAnchor(source, Number(body.startOffset), Number(body.endOffset));
|
|
110
122
|
const comment = {
|
|
111
123
|
id: randomUUID(), filePath,
|
|
@@ -155,7 +155,12 @@ export function registerProjectHistoryRoutes(app, context) {
|
|
|
155
155
|
if (!project || !canEdit(project))
|
|
156
156
|
return apiError(reply, 403, "PROJECT_EDIT_FORBIDDEN");
|
|
157
157
|
const body = request.body;
|
|
158
|
-
|
|
158
|
+
if (body?.snapshotHash !== undefined && (typeof body.snapshotHash !== "string" || !/^[a-f0-9]{64}$/.test(body.snapshotHash))) {
|
|
159
|
+
return apiError(reply, 400, "REQUEST_INVALID");
|
|
160
|
+
}
|
|
161
|
+
const snapshotHash = typeof body?.snapshotHash === "string" ? body.snapshotHash : undefined;
|
|
162
|
+
history.assertSnapshotHash(id, versionId, snapshotHash);
|
|
163
|
+
const label = body?.label === null || body?.label === "" ? null : text(body?.label, 80);
|
|
159
164
|
const version = history.setLabel(id, versionId, label);
|
|
160
165
|
if (!version)
|
|
161
166
|
return apiError(reply, 404, "HISTORY_VERSION_NOT_FOUND");
|
|
@@ -206,8 +211,15 @@ export function registerProjectHistoryRoutes(app, context) {
|
|
|
206
211
|
return apiError(reply, 403, "PROJECT_EDIT_FORBIDDEN");
|
|
207
212
|
const body = request.body;
|
|
208
213
|
const filePath = typeof body?.path === "string" ? safeRelativePath(body.path) : undefined;
|
|
214
|
+
// Current clients bind a restore to the snapshot they displayed. Accept
|
|
215
|
+
// an omitted fingerprint only for already-open clients during an upgrade.
|
|
216
|
+
if (body?.snapshotHash !== undefined && (typeof body.snapshotHash !== "string" || !/^[a-f0-9]{64}$/.test(body.snapshotHash))) {
|
|
217
|
+
return apiError(reply, 400, "REQUEST_INVALID");
|
|
218
|
+
}
|
|
219
|
+
const snapshotHash = typeof body?.snapshotHash === "string" ? body.snapshotHash : undefined;
|
|
209
220
|
return await projectMutations.runExclusive(id, "history restore", () => {
|
|
210
221
|
const currentProject = requireEditableProject(db, id, user);
|
|
222
|
+
history.assertSnapshotHash(id, versionId, snapshotHash);
|
|
211
223
|
recordHistory(id, user.id, "checkpoint");
|
|
212
224
|
const before = projectTextSnapshot(config, id);
|
|
213
225
|
const restored = history.restore(id, versionId, filePath);
|
|
@@ -10,9 +10,11 @@ import { lucideIconSvg, resolveLucideIconName } from "../lucideIcons.js";
|
|
|
10
10
|
import { accessibleProject, canEdit } from "../projects.js";
|
|
11
11
|
import { writeProjectArchive } from "../archive.js";
|
|
12
12
|
import { extractProjectZip, ZipValidationError } from "../zip.js";
|
|
13
|
-
import { HarperUnavailableError } from "../harper.js";
|
|
13
|
+
import { HarperLintSupersededError, HarperUnavailableError } from "../harper.js";
|
|
14
|
+
import { digestToken } from "../security.js";
|
|
14
15
|
import { unreadMentionCountsForProjects } from "../commentMentions.js";
|
|
15
16
|
import { commentsSummaryForProject, commentsSummaryForProjects, dictionaryWord, escapeLikePattern, now, projectJson, requireProjectOwnerPermission, tagColors, tagsForProject, tagsForProjects, text } from "./projectShared.js";
|
|
17
|
+
const clientIdPattern = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
|
|
16
18
|
/** Register project catalog, metadata, archive, dictionary, tag, export, and deletion routes. */
|
|
17
19
|
export function registerProjectCatalogRoutes(app, context) {
|
|
18
20
|
const { config, db, collaboration, projectMutations, latexCompletions, projectOutlines, harper, recordHistory } = context;
|
|
@@ -362,15 +364,45 @@ export function registerProjectCatalogRoutes(app, context) {
|
|
|
362
364
|
if (!accessibleProject(db, id, user))
|
|
363
365
|
return apiError(reply, 404, "PROJECT_NOT_FOUND");
|
|
364
366
|
const body = request.body;
|
|
365
|
-
|
|
367
|
+
const source = body?.source;
|
|
368
|
+
const sourcePath = body?.path;
|
|
369
|
+
const clientId = body?.clientId;
|
|
370
|
+
const sequence = body?.sequence;
|
|
371
|
+
if (typeof source !== "string" || typeof sourcePath !== "string") {
|
|
366
372
|
return apiError(reply, 400, "SPELLCHECK_SOURCE_INVALID");
|
|
367
|
-
|
|
373
|
+
}
|
|
374
|
+
// Accept already-open clients during a rolling update. Current clients
|
|
375
|
+
// pair a page-local UUID with a monotonically increasing sequence so a
|
|
376
|
+
// delayed HTTP request cannot replace a newer queued check.
|
|
377
|
+
if (clientId !== undefined && (typeof clientId !== "string" || !clientIdPattern.test(clientId))) {
|
|
378
|
+
return apiError(reply, 400, "SPELLCHECK_SOURCE_INVALID");
|
|
379
|
+
}
|
|
380
|
+
if (sequence !== undefined && (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence <= 0 || clientId === undefined)) {
|
|
381
|
+
return apiError(reply, 400, "SPELLCHECK_SOURCE_INVALID");
|
|
382
|
+
}
|
|
383
|
+
if (Buffer.byteLength(source, "utf8") > maxCollaborativeFileBytes(config)) {
|
|
368
384
|
return apiError(reply, 413, "SPELLCHECK_SOURCE_TOO_LARGE");
|
|
369
385
|
}
|
|
386
|
+
let filePath;
|
|
370
387
|
try {
|
|
371
|
-
|
|
388
|
+
filePath = safeRelativePath(sourcePath);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return apiError(reply, 400, "SPELLCHECK_SOURCE_INVALID");
|
|
392
|
+
}
|
|
393
|
+
// A login session is shared by browser tabs. Pair the authenticated user
|
|
394
|
+
// with the page-local client ID so each new editor can replace only its
|
|
395
|
+
// own obsolete waiting work. Legacy clients retain their former session
|
|
396
|
+
// grouping until their page is refreshed.
|
|
397
|
+
const laneClientId = clientId ?? `legacy:${digestToken(request.cookies.texlite_session ?? "")}`;
|
|
398
|
+
const lane = `${id}\0${user.id}\0${laneClientId}\0${filePath}`;
|
|
399
|
+
try {
|
|
400
|
+
return { lints: await harper.lint(source, filePath, lane, typeof sequence === "number" ? sequence : undefined) };
|
|
372
401
|
}
|
|
373
402
|
catch (error) {
|
|
403
|
+
if (error instanceof HarperLintSupersededError) {
|
|
404
|
+
return apiError(reply, 409, "SPELLCHECK_SUPERSEDED");
|
|
405
|
+
}
|
|
374
406
|
// A missing optional command is an expected fallback condition. Keep it
|
|
375
407
|
// out of normal logs while preserving diagnostics for an actual failure.
|
|
376
408
|
if (error instanceof HarperUnavailableError)
|
package/package.json
CHANGED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{u as be,r as n,j as e}from"./react-BVUjbTTn.js";import{c as p,M as H,b as x,f as l}from"./index-C0t11tlT.js";import{G as q,C as ie,T as ke}from"./x-Bq29njHL.js";import{K as ye}from"./key-round-DOAxDSfz.js";import{C as ne,R as ve}from"./ProjectWorkspace-C6GrvKN6.js";import{G as ae,M as Ce}from"./minimize-2-BWtbag2I.js";import{M as Ne,a as we}from"./minus-9eVJnugf.js";import{P as ze}from"./plus-BGXipRoG.js";import"./check-BXvzrdoz.js";import"./search-b1ORp1k8.js";import"./user-plus-CKalKSsw.js";import"./refresh-cw-Dvp3O90f.js";const Se=p("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 oe=p("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=p("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 W=p("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 Ee=p("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=p("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 Ge({open:h,project:o,onOpenChange:b,onBeforeMutation:r}){const{t:s,i18n:E}=be(),[i,u]=n.useState(null),[J,K]=n.useState([]),[T,k]=n.useState(""),[F,V]=n.useState(()=>re(o.name)),[_,ce]=n.useState(!0),[D,Z]=n.useState(""),[y,M]=n.useState(null),[m,j]=n.useState(null),[U,R]=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(""),[g,f]=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(u(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`);K(S.commits)}else K([])};n.useEffect(()=>{if(!h){document.fullscreenElement&&document.exitFullscreen().catch(()=>{}),f(!1),k("");return}N(""),B(""),M(null),f(!1),V(re(o.name)),C("load"),r().then(()=>z(!0)).catch(t=>N(t instanceof Error?t.message:s("errors.generic"))).finally(()=>C(""))},[h,o.id]),n.useEffect(()=>{const t=()=>f(document.fullscreenElement===O.current);return document.addEventListener("fullscreenchange",t),()=>document.removeEventListener("fullscreenchange",t)},[]),n.useEffect(()=>{if(!g)return;const t=c=>{c.key==="Escape"&&!document.fullscreenElement&&f(!1)};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[g]);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})});u(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"});u(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:_})});u(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})});u(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"});u(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(g){document.fullscreenElement&&await document.exitFullscreen().catch(()=>{}),f(!1);return}const t=O.current;if(t?.requestFullscreen)try{await t.requestFullscreen()}catch{}f(!0)},xe=()=>{const t=m;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:U})}),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:h,extraWide:!0,className:g?"git-dialog-modal-fullscreen":void 0,title:s("git.title"),description:s("git.description"),onOpenChange:b,footer:e.jsx("button",{onClick:()=>b(!1),children:s("common.close")}),children:e.jsxs("div",{className:"git-dialog",children:[je&&e.jsxs("div",{className:"git-loading",children:[e.jsx(x,{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(ye,{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(ne,{size:16}):e.jsx(ie,{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(oe,{size:12})]}),e.jsxs("button",{disabled:!!a||T.trim().length<20,onClick:de,children:[a==="token"&&e.jsx(x,{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(ne,{size:16}):e.jsx(ie,{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(oe,{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=>V(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(x,{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(ae,{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(x,{className:"spin",size:14}):e.jsx(ae,{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(x,{className:"spin",size:14}):e.jsx(Se,{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(x,{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(ve,{size:14}),s("git.discardChanges")]}),!i.branch&&e.jsxs("button",{disabled:!!a,onClick:()=>{R(!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")]})]})]}),y&&e.jsxs("section",{ref:O,className:`git-section git-diff-section${g?" 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:[y.title,y.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(Ne,{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(ze,{size:14})})]}),e.jsx("button",{type:"button",className:"git-diff-fullscreen",title:s(g?"git.exitFullscreenDiff":"git.fullscreenDiff"),"aria-label":s(g?"git.exitFullscreenDiff":"git.fullscreenDiff"),onClick:()=>{fe()},children:g?e.jsx(Ce,{size:15}):e.jsx(we,{size:15})})]})]}),e.jsx(Fe,{content:y.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:()=>{R(!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:!!m,title:s(m==="branch"?"git.returnTitle":"git.checkoutTitle"),description:m==="branch"?s("git.returnDescription",{branch:i?.defaultBranch??"main"}):s("git.checkoutDescription",{revision:m?.shortSha??"",message:m?.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:U?"danger":"primary",onClick:xe,children:s("git.checkout")})]}),children:e.jsxs("label",{className:"git-force-checkout",children:[e.jsx("input",{type:"checkbox",checked:U,onChange:t=>R(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:h,empty:o,fontSize:b}){return h.trim()?e.jsx("pre",{className:"git-diff",style:{fontSize:`${b}px`},children:h.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(h){return h.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"-").replace(/-+/g,"-").replace(/^[.-]+|[.-]+$/g,"").slice(0,100)||"texlite-project"}export{Ge as GitDialog};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{u as Se,r as o,j as e}from"./react-BVUjbTTn.js";import{c as Te,f as A,M as Le,b as ee,C as fe}from"./index-C0t11tlT.js";import{d as Fe,C as me}from"./index-BRT5O-Hg.js";import{H as ze,F as De}from"./hard-drive-6uYFyezl.js";import{T as ge,d as Ee}from"./x-Bq29njHL.js";import{F as re,R as pe,S as ke,i as Me}from"./ProjectWorkspace-C6GrvKN6.js";import{G as Pe,M as Ae}from"./minimize-2-BWtbag2I.js";import{M as Be,a as Re}from"./minus-9eVJnugf.js";import{P as Ve}from"./plus-BGXipRoG.js";import"./check-BXvzrdoz.js";import"./search-b1ORp1k8.js";import"./user-plus-CKalKSsw.js";import"./refresh-cw-Dvp3O90f.js";const _e=Te("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 xe(n,l,j,p="历史版本",s="当前版本",m=3){const L=l.replace(/\r\n/g,`
|
|
2
|
-
`),M=j.replace(/\r\n/g,`
|
|
3
|
-
`);if(L===M)return{diffText:"",additions:0,deletions:0,hasChanges:!1};const c=new Fe,z=c.diff_linesToChars_(L,M),S=c.diff_main(z.chars1,z.chars2,!1);c.diff_charsToLines_(S,z.lineArray);const N=[];let r=1,D=1,g=0,P=0;for(const[h,y]of S){if(!y)continue;const d=y.split(`
|
|
4
|
-
`);d[d.length-1]===""&&d.pop();for(const C of d)h===0?N.push({op:0,text:C,oldNum:r++,newNum:D++}):h===-1?(P++,N.push({op:-1,text:C,oldNum:r++,newNum:D})):h===1&&(g++,N.push({op:1,text:C,oldNum:r,newNum:D++}))}if(g===0&&P===0)return{diffText:"",additions:0,deletions:0,hasChanges:!1};const x=[];let b=-1,u=-1;for(let h=0;h<N.length;h++)if(N[h].op!==0){const y=Math.max(0,h-m),d=Math.min(N.length-1,h+m);b===-1?(b=y,u=d):y<=u+1?u=Math.max(u,d):(x.push({start:b,end:u}),b=y,u=d)}b!==-1&&x.push({start:b,end:u});const V=[`--- a/${n} (${p})`,`+++ b/${n} (${s})`];for(const h of x){const y=N.slice(h.start,h.end+1),d=y.filter($=>$.op<=0).length,C=y.filter($=>$.op>=0).length,_=y[0],J=d===0?0:_.oldNum,B=C===0?0:_.newNum;V.push(`@@ -${J},${d} +${B},${C} @@`);for(const $ of y){const T=$.op===-1?"-":$.op===1?"+":" ";V.push(`${T}${$.text}`)}}return{diffText:V.join(`
|
|
5
|
-
`),additions:g,deletions:P,hasChanges:!0}}function oe(n,l){try{const j=new Date(n);if(Number.isNaN(j.getTime()))return n;const p=new Date;return j.getFullYear()===p.getFullYear()&&j.getMonth()===p.getMonth()&&j.getDate()===p.getDate()?j.toLocaleTimeString(l,{hour:"2-digit",minute:"2-digit"}):j.toLocaleString(l,{month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit"})}catch{return n}}function ae(n,l,j){if(n.label)return n.label;const p=oe(n.createdAt,j);if(n.changedPaths.length===1)return`${n.changedPaths[0].split("/").pop()||n.changedPaths[0]} (${p})`;if(n.changedPaths.length===2){const m=n.changedPaths[0].split("/").pop()||n.changedPaths[0],L=n.changedPaths[1].split("/").pop()||n.changedPaths[1];return`${m}, ${L} (${p})`}if(n.changedPaths.length>2)return`${n.changedPaths[0].split("/").pop()||n.changedPaths[0]} +${n.changedPaths.length-1} (${p})`;const s=n.author?.name||n.author?.username;return s?`${s} @ ${p}`:`${l} (${p})`}const ye=100;function et({open:n,project:l,onOpenChange:j,onBeforeMutation:p}){const{t:s,i18n:m}=Se(),[L,M]=o.useState([]),[c,z]=o.useState(null),[S,N]=o.useState(""),[r,D]=o.useState(null),[g,P]=o.useState(""),[x,b]=o.useState(null),[u,V]=o.useState("commit"),[h,y]=o.useState(11),[d,C]=o.useState(!1),[_,J]=o.useState(""),[B,$]=o.useState(null),[T,K]=o.useState(null),[U,w]=o.useState(""),[le,Z]=o.useState(null),[W,Q]=o.useState(!1),[ce,v]=o.useState(""),se=o.useRef(null),F=o.useRef(null),E=o.useRef(null),ie=l.permission!=="read",de=l.permission==="owner",H=!!U||W;o.useEffect(()=>{if(!n){E.current!==null&&window.clearTimeout(E.current),E.current=null,F.current?.abort(),F.current=null,Q(!1),document.fullscreenElement&&document.exitFullscreen().catch(()=>{}),C(!1);return}F.current?.abort(),F.current=null;const t=new AbortController;return w("load"),v(""),b(null),Z(null),Q(!1),A(`/api/projects/${l.id}/history?limit=${ye}`,{signal:t.signal}).then(i=>{M(i.versions),z(i.stats),Z(i.nextCursor),N(a=>i.versions.some(f=>f.id===a)?a:i.versions[0]?.id??"")}).catch(i=>{te(i)||v(R(i))}).finally(()=>{t.signal.aborted||w("")}),()=>t.abort()},[n,l.id]),o.useEffect(()=>()=>{F.current?.abort(),F.current=null,E.current!==null&&window.clearTimeout(E.current)},[l.id]);const je=async()=>{const t=le;if(!t||W||U)return;F.current?.abort();const i=new AbortController;F.current=i,Q(!0),v("");try{const a=new URLSearchParams({limit:String(ye),before:t}),f=await A(`/api/projects/${l.id}/history?${a.toString()}`,{signal:i.signal});if(i.signal.aborted)return;M(O=>{const we=new Set(O.map(ne=>ne.id));return[...O,...f.versions.filter(ne=>!we.has(ne.id))]}),z(f.stats),Z(f.nextCursor)}catch(a){te(a)||v(R(a))}finally{F.current===i&&(F.current=null,Q(!1))}};o.useEffect(()=>{const t=()=>C(document.fullscreenElement===se.current);return document.addEventListener("fullscreenchange",t),()=>document.removeEventListener("fullscreenchange",t)},[]),o.useEffect(()=>{if(!d)return;const t=i=>{i.key==="Escape"&&!document.fullscreenElement&&C(!1)};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[d]),o.useEffect(()=>{if(!n||!S){D(null);return}const t=new AbortController;return w("detail"),b(null),A(`/api/projects/${l.id}/history/${S}`,{signal:t.signal}).then(i=>{D(i),J(i.version.label??"");const a=i.version.changedPaths.find(f=>i.files.some(O=>O.path===f))??"";P(a)}).catch(i=>{te(i)||v(R(i))}).finally(()=>{t.signal.aborted||w("")}),()=>t.abort()},[n,l.id,S]);const I=x?.previousVersion??null;o.useEffect(()=>{if(!n||!S||!g){b(null);return}const t=new AbortController;w("compare"),v("");const i=u==="commit"?"&against=__previous__":"";return A(`/api/projects/${l.id}/history/${S}/file?path=${encodeURIComponent(g)}${i}`,{signal:t.signal}).then(a=>{b(a)}).catch(a=>{te(a)||v(R(a))}).finally(()=>{t.signal.aborted||w(a=>a==="compare"?"":a)}),()=>t.abort()},[n,l.id,S,g,u]);const k=o.useMemo(()=>{if(!r)return[];const t=new Set(r.version.changedPaths);return r.files.filter(i=>t.has(i.path))},[r]);o.useEffect(()=>{k.length>0&&!k.some(t=>t.path===g)?P(k[0].path):k.length===0&&g&&P("")},[k,g]);const be=async()=>{if(d){document.fullscreenElement&&await document.exitFullscreen().catch(()=>{}),C(!1);return}const t=se.current;if(t?.requestFullscreen)try{await t.requestFullscreen()}catch{}C(!0)},ve=async()=>{if(r){w("label"),v("");try{const t=await A(`/api/projects/${l.id}/history/${r.version.id}`,{method:"PATCH",body:JSON.stringify({label:_.trim()||null})});D(i=>i&&{...i,version:t.version}),M(i=>i.map(a=>a.id===t.version.id?t.version:a)),z(t.stats),t.retentionScheduled&&(E.current!==null&&window.clearTimeout(E.current),E.current=window.setTimeout(()=>{E.current=null,A(`/api/projects/${l.id}/history?limit=1`).then(i=>{z(i.stats)}).catch(()=>{})},t.retentionRefreshAfterMs))}catch(t){v(R(t))}finally{w("")}}},Ne=async()=>{const t=T;if(t){K(null),w("delete"),v("");try{if(!await p())throw new Error(s("errors.collaborationUnavailable"));const i=t==="all"?`/api/projects/${l.id}/history`:`/api/projects/${l.id}/history/${t.id}`,a=await A(i,{method:"DELETE"});if(z(a.stats),t==="all")M([]),N(""),D(null),b(null),Z(null);else{const f=L.filter(O=>O.id!==t.id);M(f),S===t.id&&(N(f[0]?.id??""),D(null),b(null))}}catch(i){v(R(i))}finally{w("")}}},Ce=async()=>{const t=B;if(!(!t||!r)){$(null),w("restore"),v("");try{if(!await p())throw new Error(s("errors.collaborationUnavailable"));await A(`/api/projects/${l.id}/history/${r.version.id}/restore`,{method:"POST",body:JSON.stringify(t==="project"?{}:{path:t})}),window.location.reload()}catch(i){v(R(i)),w("")}}},X=r?.version.author?.name||r?.version.author?.username||(r?.version.reason==="autosave"?s("history.reasons.autosave"):s("editor.deletedUser")),q=r?oe(r.version.createdAt,m.resolvedLanguage):"",G=o.useMemo(()=>{if(!x||!r)return{diffText:"",additions:0,deletions:0,hasChanges:!1};if(u==="commit"){const t=x.comparison,i=x.historical,a=I?`${I.label||s(`history.reasons.${I.reason}`)} (${oe(I.createdAt,m.resolvedLanguage)})`:"/dev/null",f=`${X} @ ${q}`;return xe(x.path,t,i,a,f)}else{const t=x.historical,i=x.comparison,a=`${r.version.label||s(`history.reasons.${r.version.reason}`)} (${q})`,f=s("history.currentVersion");return xe(x.path,t,i,a,f)}},[x,r,u,I,X,q,s,m.resolvedLanguage]),he=r?s(`history.reasons.${r.version.reason}`):"",ue=r?ae(r.version,he,m.resolvedLanguage):"",$e=T&&T!=="all"?ae(T,s(`history.reasons.${T.reason}`),m.resolvedLanguage):"";return e.jsxs(e.Fragment,{children:[e.jsx(Le,{open:n,extraWide:!0,draggable:!0,className:"history-dialog-modal",title:s("history.projectSnapshots"),description:s("history.description"),onOpenChange:j,footer:e.jsx("button",{onClick:()=>j(!1),children:s("common.close")}),children:e.jsxs("div",{className:"history-dialog",children:[ce&&e.jsx("p",{className:"error history-message",children:ce}),U==="load"&&L.length===0?e.jsxs("div",{className:"history-loading",children:[e.jsx(ee,{className:"spin",size:22}),s("common.loading")]}):e.jsxs("div",{className:"history-dialog-layout",children:[e.jsxs("aside",{className:"history-timeline",children:[de&&c&&e.jsxs("div",{className:`history-stats${c.storageLimitExceeded?" exceeded":""}`,children:[e.jsxs("header",{children:[e.jsxs("span",{children:[e.jsx(ze,{size:14}),s("history.storage")]}),e.jsx("button",{type:"button",disabled:H||L.length===0,title:s("history.clearAll"),"aria-label":s("history.clearAll"),onClick:()=>K("all"),children:e.jsx(ge,{size:13})})]}),e.jsx("strong",{children:s("history.storageUsage",{used:Y(c.totalBytes),limit:Y(c.maxStorageBytes)})}),e.jsx("progress",{max:c.maxStorageBytes,value:Math.min(c.totalBytes,c.maxStorageBytes)}),e.jsx("small",{children:s("history.storageBreakdown",{objects:Y(c.objectBytes),metadata:Y(c.metadataBytes),protected:Y(c.protectedBytes)})}),e.jsxs("small",{children:[s("history.versionUsage",{count:c.ordinaryVersionCount,limit:c.maxVersions>0?c.maxVersions:"∞"}),c.labeledVersionCount>0?` · ${s("history.protectedVersions",{count:c.labeledVersionCount})}`:""]}),c.storageLimitExceeded&&e.jsx("small",{className:"history-storage-warning",children:s("history.storageExceeded")})]}),L.map(t=>{const i=s(`history.reasons.${t.reason}`),a=ae(t,i,m.resolvedLanguage),f=t.author?.name||t.author?.username||(t.reason==="autosave"?s("history.reasons.autosave"):s("editor.deletedUser"));return e.jsxs("button",{className:t.id===S?"active":"",onClick:()=>N(t.id),children:[e.jsx(re,{size:15}),e.jsxs("span",{children:[e.jsxs("div",{className:"history-item-heading",children:[e.jsx("strong",{title:a,children:a}),e.jsx("span",{className:`history-reason-badge reason-${t.reason}`,children:i})]}),e.jsx("small",{children:new Date(t.createdAt).toLocaleString(m.resolvedLanguage)}),e.jsxs("small",{children:[f," · ",s("history.changedCount",{count:t.changedPaths.length})]})]})]},t.id)}),le&&e.jsx("div",{className:"history-load-more",children:e.jsxs("button",{type:"button",disabled:H,"aria-busy":W,onClick:()=>{je()},children:[W?e.jsx(ee,{className:"spin",size:13}):e.jsx(me,{size:13}),s(W?"common.loading":"history.loadOlder")]})}),L.length===0&&e.jsx("p",{className:"muted padded",children:s("history.empty")})]}),e.jsx("section",{className:"history-detail",children:r&&e.jsxs(e.Fragment,{children:[e.jsxs("header",{className:"history-detail-header",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"history-detail-title-row",children:[e.jsx("strong",{title:ue,children:ue}),e.jsx("span",{className:`history-reason-badge reason-${r.version.reason}`,children:he})]}),e.jsxs("small",{children:[e.jsx(me,{size:12}),new Date(r.version.createdAt).toLocaleString(m.resolvedLanguage)," · ",X," · ",s("history.fileCount",{count:r.version.fileCount})]})]}),e.jsxs("span",{className:"history-detail-actions",children:[de&&e.jsxs("button",{className:"danger-text",disabled:H,onClick:()=>K(r.version),children:[e.jsx(ge,{size:14}),s("history.deleteVersion")]}),ie&&e.jsxs("button",{className:"danger-text",disabled:H,onClick:()=>$("project"),children:[e.jsx(pe,{size:14}),s("history.restoreProject")]})]})]}),ie&&e.jsxs("div",{className:"history-label",children:[e.jsxs("label",{children:[e.jsx(_e,{size:14}),e.jsx("input",{value:_,maxLength:80,placeholder:s("history.labelPlaceholder"),onChange:t=>J(t.target.value)})]}),e.jsxs("button",{disabled:H||_.trim()===(r.version.label??""),onClick:()=>{ve()},children:[U==="label"?e.jsx(ee,{className:"spin",size:13}):e.jsx(ke,{size:13}),s("history.saveLabel")]})]}),e.jsxs("div",{className:"history-files",children:[e.jsxs("div",{className:"history-files-toolbar",children:[e.jsxs("div",{className:"history-files-heading",children:[e.jsx(De,{size:13}),e.jsx("span",{children:s("history.changedFilesHeading",{count:k.length})})]}),e.jsx("div",{className:"history-file-actions",children:ie&&e.jsxs("button",{disabled:!g||H,onClick:()=>$(g),children:[e.jsx(pe,{size:13}),s("history.restoreFile")]})})]}),k.length>0?e.jsx("div",{className:"history-file-list",children:k.map(t=>e.jsxs("button",{type:"button",className:t.path===g?"active":"",title:t.path,onClick:()=>P(t.path),children:[e.jsx(Ee,{size:13}),e.jsx("span",{children:t.path})]},t.path))}):e.jsx("p",{className:"muted padded-small",children:s("history.noChangedFiles")})]}),e.jsx("div",{ref:se,className:`history-comparison${d?" is-fullscreen":""}`,children:g?e.jsxs(e.Fragment,{children:[e.jsxs("header",{children:[e.jsxs("div",{className:"history-comparison-meta",children:[e.jsx("strong",{children:g}),e.jsxs("small",{className:"history-commit-summary",children:[u==="commit"?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"history-commit-author",children:X}),e.jsx("span",{children:I?s("history.changedAt",{time:q}):s("history.createdAt",{time:q})})]}):e.jsx("span",{children:s("history.comparingCurrent")}),G.hasChanges&&e.jsxs("span",{className:"history-diff-stats",children:[e.jsxs("span",{className:"addition",children:["+",G.additions]})," ",e.jsxs("span",{className:"deletion",children:["-",G.deletions]})]})]})]}),e.jsxs("div",{className:"history-diff-controls",children:[e.jsxs("div",{className:"history-mode-toggle",role:"group","aria-label":s("history.diffMode"),children:[e.jsxs("button",{type:"button",className:u==="commit"?"active":"",onClick:()=>V("commit"),title:s("history.diffModeCommitHint"),children:[e.jsx(Pe,{size:13}),e.jsx("span",{children:s("history.diffModeCommit")})]}),e.jsxs("button",{type:"button",className:u==="current"?"active":"",onClick:()=>V("current"),title:s("history.diffModeCurrentHint"),children:[e.jsx(re,{size:13}),e.jsx("span",{children:s("history.diffModeCurrent")})]})]}),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:h<=8,title:s("git.diffFontDecrease"),"aria-label":s("git.diffFontDecrease"),onClick:()=>y(t=>Math.max(8,t-1)),children:e.jsx(Be,{size:14})}),e.jsxs("span",{className:"git-diff-font-value","aria-live":"polite",children:[h,"px"]}),e.jsx("button",{type:"button",className:"git-diff-font-button",disabled:h>=24,title:s("git.diffFontIncrease"),"aria-label":s("git.diffFontIncrease"),onClick:()=>y(t=>Math.min(24,t+1)),children:e.jsx(Ve,{size:14})})]}),e.jsx("button",{type:"button",className:"git-diff-fullscreen",title:s(d?"git.exitFullscreenDiff":"git.fullscreenDiff"),"aria-label":s(d?"git.exitFullscreenDiff":"git.fullscreenDiff"),onClick:()=>{be()},children:d?e.jsx(Ae,{size:15}):e.jsx(Re,{size:15})})]})]}),U==="compare"&&!x?e.jsxs("div",{className:"history-comparison-empty",children:[e.jsx(ee,{className:"spin",size:24}),e.jsx("span",{children:s("common.loading")})]}):G.hasChanges?e.jsx("pre",{className:"git-diff history-git-diff",style:{fontSize:`${h}px`},children:G.diffText.split(`
|
|
6
|
-
`).map((t,i)=>{const a=t.startsWith("+")&&!t.startsWith("+++")?"addition":t.startsWith("-")&&!t.startsWith("---")?"deletion":t.startsWith("@@")?"hunk":t.startsWith("diff ")||t.startsWith("---")||t.startsWith("+++")?"header":"";return e.jsxs("span",{className:a,children:[t,`
|
|
7
|
-
`]},i)})}):e.jsxs("div",{className:"git-diff-empty history-diff-empty",children:[e.jsx(Me,{size:24}),e.jsx("span",{children:s(u==="commit"?"history.noDifferencesInCommit":"history.noDifferences")})]})]}):e.jsxs("div",{className:"history-comparison-empty",children:[e.jsx(re,{size:25}),e.jsx("span",{children:k.length===0?s("history.noChangedFiles"):s("history.chooseFile")})]})})]})})]})]})}),e.jsx(fe,{open:!!B,title:s(B==="project"?"history.restoreProjectTitle":"history.restoreFileTitle"),description:B==="project"?s("history.restoreProjectDescription"):s("history.restoreFileDescription",{path:B??""}),confirmLabel:s("history.restore"),danger:!0,onCancel:()=>$(null),onConfirm:()=>{Ce()}}),e.jsx(fe,{open:!!T,title:s(T==="all"?"history.clearAllTitle":"history.deleteVersionTitle"),description:T==="all"?s("history.clearAllDescription"):s("history.deleteVersionDescription",{name:$e}),confirmLabel:s(T==="all"?"history.clearAll":"history.deleteVersion"),danger:!0,onCancel:()=>K(null),onConfirm:()=>{Ne()}})]})}function Y(n){return n<1024?`${n} B`:n<1024*1024?`${Math.round(n/102.4)/10} KB`:n<1024*1024*1024?`${Math.round(n/(1024*1024)*10)/10} MB`:`${Math.round(n/(1024*1024*1024)*10)/10} GB`}function R(n){return n instanceof Error?n.message:String(n)}function te(n){return n instanceof Error&&n.name==="AbortError"}export{et as HistoryDialog};
|