texlite 0.6.0 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/client/assets/{BinaryModule-Aj1vLnwf-C2TCdTPg.js → BinaryModule-Aj1vLnwf-FnkCpIDV.js} +1 -1
- package/dist/client/assets/{GitDialog-9mNxx8oX.js → GitDialog-BtWtaeZV.js} +1 -1
- package/dist/client/assets/{HistoryDialog-CmoD9Cvf.js → HistoryDialog-DOzAQAQA.js} +1 -1
- package/dist/client/assets/{LatexEditor-Dp8EoZBP.js → LatexEditor-C5S1jijC.js} +20 -20
- package/dist/client/assets/PdfPreview-B3-kvd02.js +1 -0
- package/dist/client/assets/{ProjectNavigationDialogs-veKVnRxc.js → ProjectNavigationDialogs-DeEMFRsp.js} +1 -1
- package/dist/client/assets/{SystemMetricsDialog-D_oiVmBC.js → SystemMetricsDialog-DWh6yS0w.js} +1 -1
- package/dist/client/assets/binary-D_GeVl1T.js +1 -0
- package/dist/client/assets/index-C4KkkecE.js +17 -0
- package/dist/client/assets/index-DwzHKaKD.css +1 -0
- package/dist/client/assets/{index-CyEG4l54.js → index-nywS6bYC.js} +1 -1
- package/dist/client/assets/{plus-Bup73Wq6.js → plus-B_Bl12ZY.js} +1 -1
- package/dist/client/assets/{spellCheck-BHW5bumN.js → spellCheck-BynsoX26.js} +3 -3
- package/dist/client/index.html +2 -2
- package/dist/server/app.js +888 -339
- package/dist/server/archive.js +20 -3
- package/dist/server/auth.js +13 -2
- package/dist/server/collaboration.js +815 -124
- package/dist/server/compileArtifacts.js +31 -13
- package/dist/server/compiler.js +69 -20
- package/dist/server/db.js +6 -0
- package/dist/server/environment.js +4 -2
- package/dist/server/files.js +172 -25
- package/dist/server/git.js +152 -124
- package/dist/server/history.js +86 -18
- package/dist/server/http.js +26 -0
- package/dist/server/index.js +39 -26
- package/dist/server/instanceLock.js +137 -0
- package/dist/server/latexCompletion.js +23 -1
- package/dist/server/latexFormatter.js +4 -2
- package/dist/server/processTree.js +31 -0
- package/dist/server/projectMutations.js +255 -0
- package/dist/server/projectOutline.js +113 -1
- package/dist/server/projectSearch.js +6 -0
- package/dist/server/routes/compile.js +505 -128
- package/dist/server/security.js +61 -1
- package/dist/server/synctex.js +11 -2
- package/dist/server/zip.js +2 -1
- package/package.json +1 -1
- package/dist/client/assets/PdfPreview-IfkmLMSU.js +0 -1
- package/dist/client/assets/binary-DTriWQzf.js +0 -1
- package/dist/client/assets/index-CI87C5Wy.js +0 -17
- package/dist/client/assets/index-DB6GC33g.css +0 -1
package/dist/server/security.js
CHANGED
|
@@ -5,7 +5,10 @@ const KEY_LENGTH = 64;
|
|
|
5
5
|
export const MIN_PASSWORD_LENGTH = 8;
|
|
6
6
|
export async function hashPassword(password) {
|
|
7
7
|
if (password.length < MIN_PASSWORD_LENGTH) {
|
|
8
|
-
throw new Error(`密码至少需要 ${MIN_PASSWORD_LENGTH} 个字符`)
|
|
8
|
+
throw Object.assign(new Error(`密码至少需要 ${MIN_PASSWORD_LENGTH} 个字符`), {
|
|
9
|
+
statusCode: 400,
|
|
10
|
+
code: "PASSWORD_TOO_SHORT"
|
|
11
|
+
});
|
|
9
12
|
}
|
|
10
13
|
const salt = randomBytes(16);
|
|
11
14
|
const key = (await scrypt(password, salt, KEY_LENGTH));
|
|
@@ -27,3 +30,60 @@ export function createSessionToken() {
|
|
|
27
30
|
export function digestToken(token) {
|
|
28
31
|
return createHash("sha256").update(token).digest("hex");
|
|
29
32
|
}
|
|
33
|
+
export class LoginRateLimiter {
|
|
34
|
+
attempts = new Map();
|
|
35
|
+
maxAttempts;
|
|
36
|
+
windowMs;
|
|
37
|
+
lockoutMs;
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.maxAttempts = options.maxAttempts ?? 5;
|
|
40
|
+
this.windowMs = options.windowMs ?? 15 * 60_000;
|
|
41
|
+
this.lockoutMs = options.lockoutMs ?? 15 * 60_000;
|
|
42
|
+
}
|
|
43
|
+
isLocked(key) {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const record = this.attempts.get(key);
|
|
46
|
+
if (!record)
|
|
47
|
+
return false;
|
|
48
|
+
if (record.lockedUntilMs > now)
|
|
49
|
+
return true;
|
|
50
|
+
if (now - record.firstAttemptMs > this.windowMs && record.lockedUntilMs <= now) {
|
|
51
|
+
this.attempts.delete(key);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
recordFailure(key) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
let record = this.attempts.get(key);
|
|
59
|
+
if (!record || (now - record.firstAttemptMs > this.windowMs && record.lockedUntilMs <= now)) {
|
|
60
|
+
record = { count: 1, firstAttemptMs: now, lockedUntilMs: 0 };
|
|
61
|
+
this.attempts.set(key, record);
|
|
62
|
+
return { locked: false, remainingAttempts: this.maxAttempts - 1, retryAfterSeconds: 0 };
|
|
63
|
+
}
|
|
64
|
+
record.count += 1;
|
|
65
|
+
if (record.count >= this.maxAttempts) {
|
|
66
|
+
record.lockedUntilMs = now + this.lockoutMs;
|
|
67
|
+
return { locked: true, remainingAttempts: 0, retryAfterSeconds: Math.ceil(this.lockoutMs / 1000) };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
locked: false,
|
|
71
|
+
remainingAttempts: Math.max(0, this.maxAttempts - record.count),
|
|
72
|
+
retryAfterSeconds: 0
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
reset(key) {
|
|
76
|
+
this.attempts.delete(key);
|
|
77
|
+
}
|
|
78
|
+
clear() {
|
|
79
|
+
this.attempts.clear();
|
|
80
|
+
}
|
|
81
|
+
prune() {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
for (const [key, record] of this.attempts.entries()) {
|
|
84
|
+
if (record.lockedUntilMs <= now && now - record.firstAttemptMs > this.windowMs) {
|
|
85
|
+
this.attempts.delete(key);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/server/synctex.js
CHANGED
|
@@ -2,6 +2,15 @@ import path from "node:path";
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
const runFile = promisify(execFile);
|
|
5
|
+
/** A user-correctable SyncTeX failure, rather than an opaque server error. */
|
|
6
|
+
export class SyncTeXError extends Error {
|
|
7
|
+
statusCode = 422;
|
|
8
|
+
code = "SYNCTEX_FAILED";
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "SyncTeXError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
5
14
|
async function runSynctex(args, cwd) {
|
|
6
15
|
try {
|
|
7
16
|
const { stdout } = await runFile("synctex", args, {
|
|
@@ -12,7 +21,7 @@ async function runSynctex(args, cwd) {
|
|
|
12
21
|
catch (error) {
|
|
13
22
|
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
14
23
|
? String(error.stderr) : "";
|
|
15
|
-
throw new
|
|
24
|
+
throw new SyncTeXError(stderr.trim() || "SyncTeX 无法定位该位置,请重新编译项目");
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
export async function sourceToPdf(sourceDirectory, pdfPath, sourcePath, line, column) {
|
|
@@ -44,7 +53,7 @@ export async function pdfToSource(sourceDirectory, pdfPath, page, x, y) {
|
|
|
44
53
|
const line = numericField(output, "Line");
|
|
45
54
|
const column = numericField(output, "Column");
|
|
46
55
|
if (!input || !line)
|
|
47
|
-
throw new
|
|
56
|
+
throw new SyncTeXError("SyncTeX 没有找到对应的源码位置");
|
|
48
57
|
const absoluteInput = path.isAbsolute(input) ? input : path.resolve(sourceDirectory, input);
|
|
49
58
|
return { input: absoluteInput, line, column: column && column > 0 ? column : 1 };
|
|
50
59
|
}
|
package/dist/server/zip.js
CHANGED
|
@@ -3,13 +3,14 @@ import path from "node:path";
|
|
|
3
3
|
import { Transform } from "node:stream";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
5
|
import yauzl from "yauzl";
|
|
6
|
-
import { safeRelativePath } from "./files.js";
|
|
6
|
+
import { assertNoSymbolicLinks, safeRelativePath } from "./files.js";
|
|
7
7
|
const MAX_ENTRIES = 1_000;
|
|
8
8
|
const MAX_TOTAL_BYTES = 200 * 1024 * 1024;
|
|
9
9
|
export async function extractProjectZip(buffer, destination, maxFileBytes) {
|
|
10
10
|
const maxTotalBytes = Math.max(MAX_TOTAL_BYTES, maxFileBytes);
|
|
11
11
|
const entries = await inspectZip(buffer, maxFileBytes, maxTotalBytes);
|
|
12
12
|
const prefix = commonRootPrefix(entries.filter((entry) => !entry.directory).map((entry) => entry.fileName));
|
|
13
|
+
assertNoSymbolicLinks(destination);
|
|
13
14
|
fs.mkdirSync(destination, { recursive: true, mode: 0o700 });
|
|
14
15
|
const files = await extractZip(buffer, destination, prefix, maxFileBytes, maxTotalBytes);
|
|
15
16
|
const mainFile = discoverMainFile(destination, files);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as V,r as i,j as n}from"./react-CsO_xvbl.js";import{g as Z,P as O,w as U,G as Y}from"./pdf-DPnwNQHb.js";import{M as F,P as X,a as H}from"./plus-Bup73Wq6.js";import{L as T}from"./index-CI87C5Wy.js";Y.workerSrc=U;const q=new O;function te({url:E,target:u,compiling:N=!1,onViewportLocation:k,onDoubleClickLocation:h}){const{t}=V(),p=i.useRef(null),[f,L]=i.useState(null),[b,x]=i.useState(0),[o,m]=i.useState(100),[P,y]=i.useState(""),z=i.useRef(new Map),v=i.useCallback((e,r)=>{r?z.current.set(e,r):z.current.delete(e)},[]),S=async e=>{if(!f||e==null)return;let r=e;if(typeof e=="string"&&(r=await f.getDestination(e).catch(()=>null)),!Array.isArray(r)||r.length===0)return;const d=r[0];let l=null;if(typeof d=="number"&&Number.isInteger(d))l=d;else if(d!=null)try{l=await f.getPageIndex(d)}catch{return}l==null||l<0||l>=f.numPages||z.current.get(l+1)?.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"})},R=()=>{const e=p.current;if(!e)return;const r=e.getBoundingClientRect(),d=r.left+r.width/2,l=r.top+r.height/2,D=[...e.querySelectorAll(".pdf-page")],s=D.find(j=>{const C=j.getBoundingClientRect();return l>=C.top&&l<=C.bottom})??D.reduce((j,C)=>{if(!j)return C;const $=Math.abs(C.getBoundingClientRect().top-l),B=Math.abs(j.getBoundingClientRect().top-l);return $<B?C:j},null);if(!s)return;const a=s.getBoundingClientRect(),c=Number(s.dataset.scale)||1,M=Number(s.dataset.page),W=Math.max(0,Math.min(a.width,d-a.left))/c,A=Math.max(0,Math.min(a.height,l-a.top))/c;M&&k(M,W,A)};i.useEffect(()=>{const e=p.current;if(!e)return;const r=()=>x(e.clientWidth);r();const d=new ResizeObserver(r);return d.observe(e),()=>d.disconnect()},[]),i.useEffect(()=>{let e=!1;L(null),y("");const r=Z({url:E,worker:q});return r.promise.then(d=>{e||L(d)}).catch(()=>{e||y(t("editor.pdfLoadFailed"))}),()=>{e=!0,r.destroy()}},[E]),i.useEffect(()=>{o>100||!p.current||(p.current.scrollLeft=0)},[o,b,f]);const g=e=>m(r=>Math.min(200,Math.max(50,r+e))),I=()=>{p.current&&(p.current.scrollLeft=0),m(100)},w=e=>{!e.shiftKey||Math.abs(e.deltaX)>Math.abs(e.deltaY)||(e.preventDefault(),e.currentTarget.scrollLeft+=e.deltaY,R())};return n.jsxs("div",{className:"pdf-viewer",children:[n.jsxs("div",{className:"pdf-toolbar",role:"toolbar","aria-label":t("editor.pdfZoomControls"),children:[n.jsx("button",{disabled:o<=50,title:t("editor.pdfZoomOut"),"aria-label":t("editor.pdfZoomOut"),onClick:()=>g(-10),children:n.jsx(F,{size:14})}),n.jsxs("span",{className:"pdf-zoom-value","aria-live":"polite",children:[o,"%"]}),n.jsx("button",{disabled:o>=200,title:t("editor.pdfZoomIn"),"aria-label":t("editor.pdfZoomIn"),onClick:()=>g(10),children:n.jsx(X,{size:14})}),n.jsxs("button",{className:"pdf-fit-width",title:t("editor.pdfFitWidth"),onClick:I,children:[n.jsx(H,{size:13}),t("editor.pdfFitWidth")]})]}),N&&n.jsxs("div",{className:"pdf-compiling-overlay",role:"status","aria-live":"polite",children:[n.jsx(T,{className:"spin",size:20}),n.jsx("span",{children:t("editor.compiling")})]}),n.jsx("div",{className:"pdf-document",ref:p,onScroll:R,onWheel:w,children:n.jsxs("div",{className:"pdf-pages",style:{width:`${Math.max(1,b-28)*Math.max(1,o/100)}px`},children:[!f&&!P&&n.jsxs("div",{className:"pdf-loading",role:"status","aria-live":"polite",children:[n.jsx(T,{className:"spin",size:24}),n.jsx("span",{children:t("editor.loadingPdf")})]}),P&&n.jsx("div",{className:"preview-empty",children:n.jsx("strong",{children:P})}),f&&b>0&&Array.from({length:f.numPages},(e,r)=>n.jsx(G,{document:f,pageNumber:r+1,availableWidth:b-28,zoom:o/100,target:u?.page===r+1?u:null,onReady:R,onPageElement:v,onInternalLink:S,onDoubleClickLocation:h},r+1))]})})]})}function G({document:E,pageNumber:u,availableWidth:N,zoom:k,target:h,onReady:t,onPageElement:p,onInternalLink:f,onDoubleClickLocation:L}){const b=i.useRef(null),x=i.useRef(null),o=i.useRef(null),[m,P]=i.useState(1),[y,z]=i.useState({width:0,height:0}),[v,S]=i.useState(u===1),[R,g]=i.useState(!1),[I,w]=i.useState(null),[e,r]=i.useState([]);i.useEffect(()=>{const s=b.current;if(!s||v)return;if(!("IntersectionObserver"in window)){S(!0);return}const a=new IntersectionObserver(c=>{c.some(M=>M.isIntersecting)&&(S(!0),a.disconnect())},{rootMargin:"900px 0px"});return a.observe(s),()=>a.disconnect()},[v]),i.useEffect(()=>{h&&S(!0)},[h?.nonce]),i.useEffect(()=>{if(!v)return;let s=!1,a=null;return g(!1),w(null),r([]),E.getPage(u).then(c=>{if(s)return;const M=c.getViewport({scale:1}),W=N/M.width,A=Math.min(3.3,Math.max(.175,W*k)),j=c.getViewport({scale:A});if(P(A),z({width:j.width,height:j.height}),w(j),c.getAnnotations({intent:"display"}).then(B=>{s||r(B)}).catch(()=>{s||r([])}),!x.current)return;const C=Math.max(1,window.devicePixelRatio||1),$=c.getViewport({scale:A*C});return x.current.width=Math.floor($.width),x.current.height=Math.floor($.height),a=c.render({canvas:x.current,viewport:$}),a.promise.then(()=>{s||(g(!0),window.requestAnimationFrame(t))})}).catch(()=>{}),()=>{s=!0,a?.cancel()}},[E,u,N,k,v]),i.useEffect(()=>{!h||!o.current||o.current.scrollIntoView({behavior:"smooth",block:"center",inline:"center"})},[h?.nonce,m]);const d=s=>{if(!L||!R||s.target instanceof HTMLElement&&s.target.closest("a"))return;const a=x.current;if(!a)return;const c=a.getBoundingClientRect();if(s.clientX<c.left||s.clientX>c.right||s.clientY<c.top||s.clientY>c.bottom)return;const M=m||1,W=Math.max(0,Math.min(c.width,s.clientX-c.left))/M,A=Math.max(0,Math.min(c.height,s.clientY-c.top))/M;L(u,W,A)},l=Math.max(1,N*k),D=l*(792/612);return n.jsxs("figure",{ref:s=>{b.current=s,p(u,s)},onDoubleClick:d,className:`pdf-page${R?" rendered":" loading"}`,"data-page":u,"data-scale":m,style:{width:`${y.width||l}px`,minHeight:`${y.height||D}px`},children:[n.jsx("canvas",{ref:x,style:{width:`${y.width||l}px`,height:`${y.height||D}px`}}),R&&I&&n.jsx(_,{annotations:e,viewport:I,onInternalLink:f}),h&&n.jsx("span",{ref:o,className:"pdf-sync-marker",style:{left:h.x*m,top:h.y*m}}),n.jsx("figcaption",{children:u})]})}function _({annotations:E,viewport:u,onInternalLink:N}){const{t:k}=V(),h=k("editor.pdfLink");return n.jsx("div",{className:"pdf-annotation-layer","aria-label":k("editor.pdfLinks"),children:E.filter(t=>t.subtype==="Link"&&Array.isArray(t.rect)&&t.rect.length>=4).map((t,p)=>{const[f,L]=u.convertToViewportPoint(t.rect[0],t.rect[1]),[b,x]=u.convertToViewportPoint(t.rect[2],t.rect[3]),o=[f,L,b,x],m=Math.min(o[0],o[2]),P=Math.min(o[1],o[3]),y=Math.abs(o[2]-o[0]),z=Math.abs(o[3]-o[1]),v=typeof t.title=="string"?t.title.trim():"",S=typeof t.unsafeUrl=="string"?t.unsafeUrl.trim():"",g=(typeof t.url=="string"?t.url.trim():"")||(/^(?:https?|mailto|ftp):/i.test(S)?S:""),I=t.dest;if(!g&&I==null)return null;const w=!!g;return n.jsx("a",{className:"pdf-annotation-link",href:g||"#",target:w?"_blank":void 0,rel:w?"noreferrer":void 0,title:v||(w?g:h),"aria-label":v||(w?g:h),style:{left:m,top:P,width:y,height:z},onClick:w?void 0:e=>{e.preventDefault(),N(I)}},`${p}-${m}-${P}`)})})}export{te as PdfPreview};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{B as r}from"./BinaryModule-Aj1vLnwf-C2TCdTPg.js";import"./index-CI87C5Wy.js";import"./react-CsO_xvbl.js";const a=""+new URL("/assets/harper_wasm_bg-LWXDb7Ll.wasm",import.meta.url).href,o=r.create(a,"full");export{o as binary};
|