texlite 0.9.2 → 0.9.4
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/DESIGN.md +29 -6
- package/OPERATIONS.md +9 -7
- package/README.md +6 -3
- package/README.zh-CN.md +6 -2
- package/dist/client/assets/AdminUsers-B12FJUMy.js +1 -0
- package/dist/client/assets/CitationLibraryDialog-BTmnCP4V.js +1 -0
- package/dist/client/assets/Dashboard-JA5_M3TK.js +2 -0
- package/dist/client/assets/{GitDialog-CTWAU8q6.js → GitDialog--MoUIW0n.js} +1 -1
- package/dist/client/assets/{HistoryDialog-Mo4p8HEg.js → HistoryDialog-LgKbjsTZ.js} +1 -1
- package/dist/client/assets/LatexEditor-Dj2zIaQm.js +138 -0
- package/dist/client/assets/{PdfPreview-BUpELZd-.js → PdfPreview-C_W7w360.js} +1 -1
- package/dist/client/assets/ProjectIconPickerDialog-GzRuM0oQ.js +1 -0
- package/dist/client/assets/{ProjectNavigationDialogs-CpjkXZTz.js → ProjectNavigationDialogs-BiArHmQ9.js} +1 -1
- package/dist/client/assets/ProjectSettings-DgJohWUv.js +1 -0
- package/dist/client/assets/ProjectWorkspace-C6sYiBF2.js +32 -0
- package/dist/client/assets/{SelectionHistoryDialog-9FWlc3Rb.js → SelectionHistoryDialog-Bpv6nvrC.js} +2 -2
- package/dist/client/assets/SystemMetricsDialog-hJQATmd4.js +1 -0
- package/dist/client/assets/{TagManagementDialog-BHIA0vEA.js → TagManagementDialog-BHC8qULq.js} +1 -1
- package/dist/client/assets/{WordCountDialog-DFIWvLrP.js → WordCountDialog-DZhhO94k.js} +1 -1
- package/dist/client/assets/check-C1db8iNn.js +1 -0
- package/dist/client/assets/{editor-3rgn4IYh.js → editor-B9HNwtsd.js} +1 -1
- package/dist/client/assets/hard-drive-BWZnOgnm.js +1 -0
- package/dist/client/assets/index-BiwJj0Np.css +1 -0
- package/dist/client/assets/{index-CNTGCh7t.js → index-kTamGfTl.js} +1 -1
- package/dist/client/assets/index-p1NHkGGG.js +12 -0
- package/dist/client/assets/{key-round-BjL9H8-H.js → key-round-CoDwyVJc.js} +1 -1
- package/dist/client/assets/{minimize-2-DDCq8c49.js → minimize-2-D100opW2.js} +1 -1
- package/dist/client/assets/{minus-DgKZCT9F.js → minus-DDTYB-Oz.js} +1 -1
- package/dist/client/assets/{plus-CezUzSIZ.js → plus-BVXOlQXP.js} +1 -1
- package/dist/client/assets/{refresh-cw-CDFNL-qu.js → refresh-cw-LtwnqlWh.js} +1 -1
- package/dist/client/assets/{search-CrDJjHoj.js → search-DXo_aAp0.js} +1 -1
- package/dist/client/assets/{sigma-DOcJr7VG.js → sigma-BgvHGPto.js} +1 -1
- package/dist/client/assets/{spellCheck-BgWyesvf.js → spellCheck-BtRppKcC.js} +1 -1
- package/dist/client/assets/{tags-BedQ75lh.js → tags-m9kppdi0.js} +1 -1
- package/dist/client/assets/{type-glZNdzZR.js → type-YO4aprqE.js} +1 -1
- package/dist/client/assets/{user-plus-gQ6q5Nom.js → user-plus-MRmKeDtX.js} +1 -1
- package/dist/client/assets/x-eisRQJv_.js +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/pdf-download.svg +2 -0
- package/dist/client/pdf-file.svg +19 -0
- package/dist/client/tex-file.svg +7 -0
- package/dist/server/cli.js +5 -2
- package/dist/server/collaboration.js +6 -19
- package/dist/server/db.js +91 -10
- package/dist/server/latexDocumentGraph.js +127 -16
- package/dist/server/projectOutline.js +103 -91
- package/dist/server/projectReferences.js +1 -1
- package/dist/server/routes/comments.js +10 -4
- package/dist/server/routes/projectFiles.js +6 -1
- package/dist/server/routes/projectShared.js +33 -12
- package/dist/shared/collaborationProtocol.js +31 -0
- package/dist/shared/latexDependencies.js +83 -23
- package/dist/shared/writingChecks.js +2 -2
- package/package.json +4 -3
- package/dist/client/assets/AdminUsers-Buf5ZclR.js +0 -1
- package/dist/client/assets/CitationLibraryDialog-DrKbIlD-.js +0 -1
- package/dist/client/assets/Dashboard-DLk2j3oq.js +0 -2
- package/dist/client/assets/LatexEditor-CwvakYva.js +0 -135
- package/dist/client/assets/ProjectIconPickerDialog-BiWSwDdt.js +0 -1
- package/dist/client/assets/ProjectSettings-DoxitHbX.js +0 -1
- package/dist/client/assets/ProjectWorkspace-iNqXZlsj.js +0 -29
- package/dist/client/assets/SystemMetricsDialog-24kifZO6.js +0 -1
- package/dist/client/assets/check-C3GZh6a1.js +0 -1
- package/dist/client/assets/chevron-left-l1oupvGO.js +0 -1
- package/dist/client/assets/hard-drive-B-NSDVVA.js +0 -1
- package/dist/client/assets/index-CrlfruX_.css +0 -1
- package/dist/client/assets/index-DIM_KiG2.js +0 -12
- package/dist/client/assets/x-CSzFikn2.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"./index-
|
|
1
|
+
import{c as e}from"./index-p1NHkGGG.js";const n=e("Maximize2",[["polyline",{points:"15 3 21 3 21 9",key:"mznyad"}],["polyline",{points:"9 21 3 21 3 15",key:"1avn1i"}],["line",{x1:"21",x2:"14",y1:"3",y2:"10",key:"ota7mn"}],["line",{x1:"3",x2:"10",y1:"21",y2:"14",key:"1atl0r"}]]);const a=e("Minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]);export{a as M,n as a};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"./index-
|
|
1
|
+
import{c as e}from"./index-p1NHkGGG.js";const a=e("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);export{a as P};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"./index-
|
|
1
|
+
import{c as e}from"./index-p1NHkGGG.js";const t=e("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);export{t as R};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c}from"./index-
|
|
1
|
+
import{c}from"./index-p1NHkGGG.js";const r=c("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);export{r as S};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as a}from"./index-
|
|
1
|
+
import{c as a}from"./index-p1NHkGGG.js";const e=a("Sigma",[["path",{d:"M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2",key:"wuwx1p"}]]);export{e as S};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as w,A as L}from"./index-
|
|
1
|
+
import{g as w,A as L}from"./index-p1NHkGGG.js";import{s as y,c as S}from"./ProjectWorkspace-C6sYiBF2.js";import"./react-BVUjbTTn.js";import"./check-C1db8iNn.js";import"./x-eisRQJv_.js";import"./search-DXo_aAp0.js";import"./user-plus-MRmKeDtX.js";import"./refresh-cw-LtwnqlWh.js";const k=5,f=new Map,C=12,x=S();let E=0;class p extends Error{constructor(){super("A newer writing check replaced this request."),this.name="HarperLintSupersededError"}}function I(e){return e instanceof p}function $(e){const t=[0];let r=0;for(const n of e)r+=n.length,t.push(r);return t}function d(e){return[...new Set(e.map(t=>t.trim()).filter(Boolean))].sort((t,r)=>t.localeCompare(r)).join("\0")}async function v(e,t,r,n){try{return(await w(`/api/projects/${e}/spellcheck`,{method:"POST",body:JSON.stringify({path:t,source:r,clientId:x,sequence:n})})).lints}catch(o){throw o instanceof L&&o.code==="SPELLCHECK_SUPERSEDED"?new p:o}}function A(e,t,r){if(!t.problem)return null;const n={from:t.start,to:t.end},o={from:r[t.start]??-1,to:r[t.end]??-1},i=c=>c.from>=0&&c.to>c.from&&c.to<=e.length,a=c=>i(c)&&e.slice(c.from,c.to)===t.problem;return a(o)?o:a(n)?n:null}function H(e,t,r){const n=A(e,t,r);if(!n)return null;const{from:o,to:i}=n,a=e.slice(o,i);if(!a.trim()||!/[A-Za-z]/.test(a)||t.kind==="Formatting")return null;const c=t.kind==="Spelling"||t.kind==="Typo"?"spelling":"grammar",u=t.suggestions.filter((m,s,l)=>s<k&&l.indexOf(m)===s);return{from:o,to:i,word:a,kind:c,message:t.message,suggestions:u}}function h(e,t){let r=2166136261;for(let n=0;n<t.length;n+=1)r^=t.charCodeAt(n),r=Math.imul(r,16777619);return`${e}\0${t.length}:${r>>>0}`}function g(e,t,r){const n=h(e,t),o=f.get(n);return!o||o.path!==e||o.source!==t||o.dictionary!==r?null:(f.delete(n),f.set(n,o),o.issues)}function R(e,t,r,n){const o=h(e,t);for(f.set(o,{path:e,source:t,dictionary:r,issues:n});f.size>C;){const i=f.keys().next().value;if(i===void 0)break;f.delete(i)}}async function b(e,t,r,n="main.tex"){const o=d(t),i=g(n,e,o);if(i)return i;const a=$(e),c=new Set(t.map(s=>s.trim().toLocaleLowerCase("en-US")).filter(Boolean)),u=new Set,m=r.map(s=>H(e,s,a)).filter(s=>!!s).filter(s=>s.kind!=="spelling"||!c.has(s.word.trim().toLocaleLowerCase("en-US"))).filter(s=>{const l=`${s.from}:${s.to}:${s.kind}`;return u.has(l)?!1:(u.add(l),!0)}).sort((s,l)=>s.from-l.from||s.to-l.to);return R(n,e,o,m),m}async function P(e,t,r,n){const o=d(n),i=g(t,r,o);if(i)return i;const a=await v(e,t,r,++E);return b(r,n,a,t)}function N(e,t,r,n=[]){return y(t)?P(e,t,r,n):Promise.resolve([])}export{p as HarperLintSupersededError,I as isHarperLintSupersededError,N as lintLatex,b as mapLatexLints};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as a}from"./index-
|
|
1
|
+
import{c as a}from"./index-p1NHkGGG.js";const e=a("Tags",[["path",{d:"m15 5 6.3 6.3a2.4 2.4 0 0 1 0 3.4L17 19",key:"1cbfv1"}],["path",{d:"M9.586 5.586A2 2 0 0 0 8.172 5H3a1 1 0 0 0-1 1v5.172a2 2 0 0 0 .586 1.414L8.29 18.29a2.426 2.426 0 0 0 3.42 0l3.58-3.58a2.426 2.426 0 0 0 0-3.42z",key:"135mg7"}],["circle",{cx:"6.5",cy:"9.5",r:".5",fill:"currentColor",key:"5pm5xn"}]]);export{e as T};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"./index-
|
|
1
|
+
import{c as e}from"./index-p1NHkGGG.js";const n=e("Type",[["polyline",{points:"4 7 4 4 20 4 20 7",key:"1nosan"}],["line",{x1:"9",x2:"15",y1:"20",y2:"20",key:"swin9y"}],["line",{x1:"12",x2:"12",y1:"4",y2:"20",key:"1tx1rr"}]]);export{n as T};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"./index-
|
|
1
|
+
import{c as e}from"./index-p1NHkGGG.js";const c=e("UserPlus",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14",key:"1bvyxn"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]]);export{c as U};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as a}from"./index-p1NHkGGG.js";const t=a("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);const h=a("AtSign",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8",key:"7n84p3"}]]);const y=a("BookMarked",[["path",{d:"M10 2v8l3-3 3 3V2",key:"sqw3rj"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20",key:"k3hazp"}]]);const k=a("BookOpen",[["path",{d:"M12 7v14",key:"1akyts"}],["path",{d:"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",key:"ruj8y"}]]);const d=a("ChevronLeft",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]);const p=a("ChevronRight",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]);const c=a("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);const s=a("FileArchive",[["path",{d:"M10 12v-1",key:"v7bkov"}],["path",{d:"M10 18v-2",key:"1cjy8d"}],["path",{d:"M10 7V6",key:"dljcrl"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M15.5 22H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v16a2 2 0 0 0 .274 1.01",key:"gkbcor"}],["circle",{cx:"10",cy:"20",r:"2",key:"1xzdoj"}]]);const r=a("FileCode2",[["path",{d:"M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4",key:"1pf5j1"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"m5 12-3 3 3 3",key:"oke12k"}],["path",{d:"m9 18 3-3-3-3",key:"112psh"}]]);const o=a("FileSearch",[["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3",key:"ms7g94"}],["path",{d:"m9 18-1.5-1.5",key:"1j6qii"}],["circle",{cx:"5",cy:"14",r:"3",key:"ufru5t"}]]);const l=a("FileSpreadsheet",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M8 13h2",key:"yr2amv"}],["path",{d:"M14 13h2",key:"un5t4a"}],["path",{d:"M8 17h2",key:"2yhykz"}],["path",{d:"M14 17h2",key:"10kma7"}]]);const n=a("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]);const M=a("FolderPlus",[["path",{d:"M12 10v6",key:"1bos4e"}],["path",{d:"M9 13h6",key:"1uhe8q"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]]);const i=a("Folder",[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]]);const v=a("GitBranch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);const x=a("ListTree",[["path",{d:"M21 12h-8",key:"1bmf0i"}],["path",{d:"M21 6H8",key:"1pqkrb"}],["path",{d:"M21 18h-8",key:"1tm79t"}],["path",{d:"M3 6v4c0 1.1.9 2 2 2h3",key:"1ywdgy"}],["path",{d:"M3 10v6c0 1.1.9 2 2 2h3",key:"2wc746"}]]);const m=a("MessageSquare",[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]]);const H=a("NotepadText",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M12 2v4",key:"3427ic"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"16",height:"18",x:"4",y:"4",rx:"2",key:"1u9h20"}],["path",{d:"M8 10h6",key:"3oa6kw"}],["path",{d:"M8 14h8",key:"1fgep2"}],["path",{d:"M8 18h5",key:"17enja"}]]);const q=a("Pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);const u=a("ScrollText",[["path",{d:"M15 12h-5",key:"r7krc0"}],["path",{d:"M15 8h-5",key:"1khuty"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4",key:"zz82l3"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",key:"1ph1d7"}]]);const g=a("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);const f=a("TriangleAlert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);const b=a("Upload",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]);const w=a("Users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["path",{d:"M16 3.13a4 4 0 0 1 0 7.75",key:"1da9ce"}]]);const z=a("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);export{h as A,y as B,p as C,c as D,o as F,v as G,x as L,m as M,H as N,q as P,u as S,g as T,w as U,z as X,i as a,l as b,r as c,k as d,n as e,t as f,b as g,M as h,d as i,s as j,f as k};
|
package/dist/client/index.html
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka@5.2.5/400.css" />
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="logo.svg" />
|
|
14
14
|
<title>TexLite</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-p1NHkGGG.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/react-BVUjbTTn.js">
|
|
17
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
17
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BiwJj0Np.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
+
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_pdf2</title><path d="M24.1,2.072h0l5.564,5.8V29.928H8.879V30H29.735V7.945L24.1,2.072" style="fill:#909090"/><path d="M24.031,2H8.808V29.928H29.664V7.873L24.03,2" style="fill:#f4f4f4"/><path d="M8.655,3.5H2.265v6.827h20.1V3.5H8.655" style="fill:#7a7b7c"/><path d="M22.472,10.211H2.395V3.379H22.472v6.832" style="fill:#dd2025"/><path d="M9.052,4.534h-.03l-.207,0H7.745v4.8H8.773V7.715L9,7.728a2.042,2.042,0,0,0,.647-.117,1.427,1.427,0,0,0,.493-.291,1.224,1.224,0,0,0,.335-.454,2.13,2.13,0,0,0,.105-.908,2.237,2.237,0,0,0-.114-.644,1.173,1.173,0,0,0-.687-.65A2.149,2.149,0,0,0,9.37,4.56a2.232,2.232,0,0,0-.319-.026M8.862,6.828l-.089,0V5.348h.193a.57.57,0,0,1,.459.181.92.92,0,0,1,.183.558c0,.246,0,.469-.222.626a.942.942,0,0,1-.524.114" style="fill:#464648"/><path d="M12.533,4.521c-.111,0-.219.008-.295.011L12,4.538h-.78v4.8h.918a2.677,2.677,0,0,0,1.028-.175,1.71,1.71,0,0,0,.68-.491,1.939,1.939,0,0,0,.373-.749,3.728,3.728,0,0,0,.114-.949,4.416,4.416,0,0,0-.087-1.127,1.777,1.777,0,0,0-.4-.733,1.63,1.63,0,0,0-.535-.4,2.413,2.413,0,0,0-.549-.178,1.282,1.282,0,0,0-.228-.017m-.182,3.937-.1,0V5.392h.013a1.062,1.062,0,0,1,.6.107,1.2,1.2,0,0,1,.324.4,1.3,1.3,0,0,1,.142.526c.009.22,0,.4,0,.549a2.926,2.926,0,0,1-.033.513,1.756,1.756,0,0,1-.169.5,1.13,1.13,0,0,1-.363.36.673.673,0,0,1-.416.106" style="fill:#464648"/><path d="M17.43,4.538H15v4.8h1.028V7.434h1.3V6.542h-1.3V5.43h1.4V4.538" style="fill:#464648"/><path d="M21.781,20.255s3.188-.578,3.188.511S22.994,21.412,21.781,20.255Zm-2.357.083a7.543,7.543,0,0,0-1.473.489l.4-.9c.4-.9.815-2.127.815-2.127a14.216,14.216,0,0,0,1.658,2.252,13.033,13.033,0,0,0-1.4.288Zm-1.262-6.5c0-.949.307-1.208.546-1.208s.508.115.517.939a10.787,10.787,0,0,1-.517,2.434A4.426,4.426,0,0,1,18.161,13.841ZM13.513,24.354c-.978-.585,2.051-2.386,2.6-2.444C16.11,21.911,14.537,24.966,13.513,24.354ZM25.9,20.895c-.01-.1-.1-1.207-2.07-1.16a14.228,14.228,0,0,0-2.453.173,12.542,12.542,0,0,1-2.012-2.655,11.76,11.76,0,0,0,.623-3.1c-.029-1.2-.316-1.888-1.236-1.878s-1.054.815-.933,2.013a9.309,9.309,0,0,0,.665,2.338s-.425,1.323-.987,2.639-.946,2.006-.946,2.006a9.622,9.622,0,0,0-2.725,1.4c-.824.767-1.159,1.356-.725,1.945.374.508,1.683.623,2.853-.91a22.549,22.549,0,0,0,1.7-2.492s1.784-.489,2.339-.623,1.226-.24,1.226-.24,1.629,1.639,3.2,1.581,1.495-.939,1.485-1.035" style="fill:#dd2025"/><path d="M23.954,2.077V7.95h5.633L23.954,2.077Z" style="fill:#909090"/><path d="M24.031,2V7.873h5.633L24.031,2Z" style="fill:#f4f4f4"/><path d="M8.975,4.457h-.03l-.207,0H7.668v4.8H8.7V7.639l.228.013a2.042,2.042,0,0,0,.647-.117,1.428,1.428,0,0,0,.493-.291A1.224,1.224,0,0,0,10.4,6.79a2.13,2.13,0,0,0,.105-.908,2.237,2.237,0,0,0-.114-.644,1.173,1.173,0,0,0-.687-.65,2.149,2.149,0,0,0-.411-.105,2.232,2.232,0,0,0-.319-.026M8.785,6.751l-.089,0V5.271H8.89a.57.57,0,0,1,.459.181.92.92,0,0,1,.183.558c0,.246,0,.469-.222.626a.942.942,0,0,1-.524.114" style="fill:#fff"/><path d="M12.456,4.444c-.111,0-.219.008-.295.011l-.235.006h-.78v4.8h.918a2.677,2.677,0,0,0,1.028-.175,1.71,1.71,0,0,0,.68-.491,1.939,1.939,0,0,0,.373-.749,3.728,3.728,0,0,0,.114-.949,4.416,4.416,0,0,0-.087-1.127,1.777,1.777,0,0,0-.4-.733,1.63,1.63,0,0,0-.535-.4,2.413,2.413,0,0,0-.549-.178,1.282,1.282,0,0,0-.228-.017m-.182,3.937-.1,0V5.315h.013a1.062,1.062,0,0,1,.6.107,1.2,1.2,0,0,1,.324.4,1.3,1.3,0,0,1,.142.526c.009.22,0,.4,0,.549a2.926,2.926,0,0,1-.033.513,1.756,1.756,0,0,1-.169.5,1.13,1.13,0,0,1-.363.36.673.673,0,0,1-.416.106" style="fill:#fff"/><path d="M17.353,4.461h-2.43v4.8h1.028V7.357h1.3V6.465h-1.3V5.353h1.4V4.461" style="fill:#fff"/></svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
2
|
+
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
|
3
|
+
<svg fill="#26a269" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 360 360" xml:space="preserve" stroke="#26a269">
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
package/dist/server/cli.js
CHANGED
|
@@ -92,6 +92,9 @@ function writeInitialConfig(configPath, siteName, adminEmail) {
|
|
|
92
92
|
const effectiveDataDirectory = configuredDataDirectory
|
|
93
93
|
? path.resolve(path.dirname(configPath), configuredDataDirectory)
|
|
94
94
|
: dataDirectory;
|
|
95
|
+
const configuredPort = Number.parseInt(process.env.TEXLITE_PORT ?? "", 10);
|
|
96
|
+
const initialPort = Number.isInteger(configuredPort) && configuredPort >= 1 && configuredPort <= 65_535
|
|
97
|
+
? configuredPort : CONFIG_DEFAULTS.port;
|
|
95
98
|
fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
96
99
|
// Data parents are not guaranteed to exist on a fresh account. Create only
|
|
97
100
|
// the parent here; the configured data directory itself is created by
|
|
@@ -107,8 +110,8 @@ function writeInitialConfig(configPath, siteName, adminEmail) {
|
|
|
107
110
|
siteName,
|
|
108
111
|
adminEmail,
|
|
109
112
|
sessionDays: CONFIG_DEFAULTS.sessionDays,
|
|
110
|
-
server: { host: CONFIG_DEFAULTS.host, port:
|
|
111
|
-
storage: { dataDir: dataDirectory },
|
|
113
|
+
server: { host: process.env.TEXLITE_HOST?.trim() || CONFIG_DEFAULTS.host, port: initialPort, basePath: CONFIG_DEFAULTS.basePath },
|
|
114
|
+
storage: { dataDir: configuredDataDirectory || dataDirectory },
|
|
112
115
|
uploads: { maxFileSizeMB: CONFIG_DEFAULTS.maxFileSizeMB },
|
|
113
116
|
pdf: {
|
|
114
117
|
loadingStrategy: CONFIG_DEFAULTS.pdfLoadingStrategy,
|
|
@@ -12,26 +12,14 @@ import { listProjectFilesAsync, outputRoot, resolveSourcePath, safeRelativePath,
|
|
|
12
12
|
import { collaborationProjectAccessFromStatement, canEdit, prepareCollaborationProjectAccessStatement } from "./projects.js";
|
|
13
13
|
import { reanchorFileComments } from "./anchors.js";
|
|
14
14
|
import { hashText } from "./editHistory.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const MESSAGE_QUERY_AWARENESS =
|
|
18
|
-
const MESSAGE_FLUSH = 4;
|
|
19
|
-
const MESSAGE_PROTOCOL = 5;
|
|
20
|
-
const MESSAGE_MAINTENANCE = 6;
|
|
21
|
-
const MESSAGE_PERMISSION = 7;
|
|
22
|
-
// A small, non-Yjs handshake message used for ephemeral compile metadata.
|
|
23
|
-
// Source text still uses the normal Yjs sync protocol; this message prevents
|
|
24
|
-
// an old IndexedDB metadata entry from being treated as an active compile.
|
|
25
|
-
const MESSAGE_COMPILE_STATES = 8;
|
|
26
|
-
// Ephemeral, per-file formatter leases. These are deliberately kept out of
|
|
27
|
-
// the Yjs document and source persistence state.
|
|
28
|
-
const MESSAGE_FORMAT_LEASE = 9;
|
|
15
|
+
import { COLLABORATION_PROTOCOL_VERSION, CollaborationMessageType, isCollaborationCleanMode, isCollaborationCompileStatus } from "../shared/collaborationProtocol.js";
|
|
16
|
+
export { COLLABORATION_PROTOCOL_VERSION } from "../shared/collaborationProtocol.js";
|
|
17
|
+
const { Sync: MESSAGE_SYNC, Awareness: MESSAGE_AWARENESS, QueryAwareness: MESSAGE_QUERY_AWARENESS, Flush: MESSAGE_FLUSH, Protocol: MESSAGE_PROTOCOL, Maintenance: MESSAGE_MAINTENANCE, Permission: MESSAGE_PERMISSION, CompileStates: MESSAGE_COMPILE_STATES, FormatLease: MESSAGE_FORMAT_LEASE } = CollaborationMessageType;
|
|
29
18
|
/**
|
|
30
19
|
* Increment this when a wire-level collaboration change cannot be decoded by
|
|
31
20
|
* an older browser. The epoch marker is rotated at the same time, which makes
|
|
32
21
|
* already-open older pages discard their local draft and reload safely.
|
|
33
22
|
*/
|
|
34
|
-
export const COLLABORATION_PROTOCOL_VERSION = 3;
|
|
35
23
|
const VERSIONED_EPOCH_PREFIX = `${COLLABORATION_PROTOCOL_VERSION}:`;
|
|
36
24
|
const SOURCE_PREFIX = "source:";
|
|
37
25
|
const MAX_PROJECT_SESSIONS = 10;
|
|
@@ -1576,7 +1564,7 @@ export function maxCollaborativeFileBytes(config) {
|
|
|
1576
1564
|
return Math.min(config.maxUploadBytes, MAX_COLLABORATIVE_FILE_BYTES);
|
|
1577
1565
|
}
|
|
1578
1566
|
export function isCollaborativeTextFile(filePath) {
|
|
1579
|
-
return /(?:\.tex|\.bib|\.sty|\.cls|\.txt|\.md|latexmkrc)$/i.test(filePath);
|
|
1567
|
+
return /(?:\.tex|\.bib|\.bst|\.sty|\.cls|\.txt|\.md|latexmkrc)$/i.test(filePath);
|
|
1580
1568
|
}
|
|
1581
1569
|
function collaborativeEntrySignature(entry) {
|
|
1582
1570
|
return `${entry.path}\0${entry.size ?? 0}\0${entry.mtimeMs ?? 0}`;
|
|
@@ -1760,9 +1748,8 @@ function isSharedCompileState(mainFile, value) {
|
|
|
1760
1748
|
const requestedBy = state.requestedBy;
|
|
1761
1749
|
return state.mainFile === mainFile
|
|
1762
1750
|
&& typeof state.runId === "string" && state.runId.length > 0
|
|
1763
|
-
&& (state.status
|
|
1764
|
-
|
|
1765
|
-
&& (state.status !== "cleaned" || state.cleanMode === "cache" || state.cleanMode === "artifacts")
|
|
1751
|
+
&& isCollaborationCompileStatus(state.status)
|
|
1752
|
+
&& (state.status !== "cleaned" || isCollaborationCleanMode(state.cleanMode))
|
|
1766
1753
|
&& (state.stale === undefined || typeof state.stale === "boolean")
|
|
1767
1754
|
&& typeof state.updatedAt === "string"
|
|
1768
1755
|
&& Boolean(requestedBy && typeof requestedBy === "object"
|
package/dist/server/db.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
|
+
const migrationsTable = "texlite_schema_migrations";
|
|
4
|
+
/**
|
|
5
|
+
* Keep this list append-only. Existing migration bodies must never be changed
|
|
6
|
+
* after release: a database records the version only after that migration's
|
|
7
|
+
* transaction has committed.
|
|
8
|
+
*
|
|
9
|
+
* Version 1 deliberately folds the untracked historical schema setup into a
|
|
10
|
+
* single baseline. It upgrades every database released before versioned
|
|
11
|
+
* migrations while preserving the current schema for new installations.
|
|
12
|
+
*/
|
|
13
|
+
const databaseMigrations = [
|
|
14
|
+
// Append future migrations below this entry. Never insert before or modify
|
|
15
|
+
// the released baseline: recorded databases will intentionally skip it.
|
|
16
|
+
{ version: 1, name: "baseline_schema_and_legacy_upgrade", apply: applyBaselineMigration }
|
|
17
|
+
];
|
|
3
18
|
export function openDatabase(config) {
|
|
4
19
|
fs.mkdirSync(config.dataDir, { recursive: true, mode: 0o700 });
|
|
5
20
|
fs.mkdirSync(config.projectsDir, { recursive: true, mode: 0o700 });
|
|
@@ -8,10 +23,67 @@ export function openDatabase(config) {
|
|
|
8
23
|
// per-transaction WAL fsync: a sudden host failure can lose the most recent
|
|
9
24
|
// acknowledged transaction, but the database remains consistent.
|
|
10
25
|
db.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000;");
|
|
11
|
-
|
|
26
|
+
try {
|
|
27
|
+
migrate(db);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// A failed migration must not leave an open SQLite handle holding a lock
|
|
31
|
+
// while startup unwinds. Its transaction has already been rolled back.
|
|
32
|
+
db.close();
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
12
35
|
return db;
|
|
13
36
|
}
|
|
14
37
|
function migrate(db) {
|
|
38
|
+
const context = {
|
|
39
|
+
migrateLegacyProjectTags: tableExists(db, "project_tags") && !tableExists(db, "user_tags"),
|
|
40
|
+
backfillAdminCanCreateProjects: missingColumn(db, "users", "can_create_projects"),
|
|
41
|
+
backfillProjectLastModifiedBy: missingColumn(db, "projects", "last_modified_by"),
|
|
42
|
+
backfillCompileRunMainFile: missingColumn(db, "compile_runs", "main_file"),
|
|
43
|
+
backfillEditSegmentBytes: missingColumn(db, "project_edit_segments", "steps_bytes")
|
|
44
|
+
};
|
|
45
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${migrationsTable} (
|
|
46
|
+
version INTEGER PRIMARY KEY,
|
|
47
|
+
name TEXT NOT NULL,
|
|
48
|
+
applied_at TEXT NOT NULL
|
|
49
|
+
)`);
|
|
50
|
+
const applied = db.prepare(`SELECT version, name FROM ${migrationsTable}`).all()
|
|
51
|
+
.map((migration) => ({ version: Number(migration.version), name: migration.name }));
|
|
52
|
+
const knownMigrations = new Map(databaseMigrations.map((migration) => [migration.version, migration]));
|
|
53
|
+
const unknownMigration = applied.find((migration) => !knownMigrations.has(migration.version));
|
|
54
|
+
if (unknownMigration !== undefined) {
|
|
55
|
+
const latestVersion = databaseMigrations.at(-1)?.version ?? 0;
|
|
56
|
+
const direction = unknownMigration.version > latestVersion ? "newer than" : "not recognized by";
|
|
57
|
+
throw new Error(`Database schema version ${unknownMigration.version} is ${direction} this TexLite release.`);
|
|
58
|
+
}
|
|
59
|
+
const renamedMigration = applied.find((migration) => knownMigrations.get(migration.version)?.name !== migration.name);
|
|
60
|
+
if (renamedMigration) {
|
|
61
|
+
throw new Error(`Database migration ${renamedMigration.version} does not match this TexLite release.`);
|
|
62
|
+
}
|
|
63
|
+
const appliedVersions = new Set(applied.map((migration) => migration.version));
|
|
64
|
+
const missingBeforeApplied = databaseMigrations.find((migration) => !appliedVersions.has(migration.version)
|
|
65
|
+
&& applied.some((appliedMigration) => appliedMigration.version > migration.version));
|
|
66
|
+
if (missingBeforeApplied) {
|
|
67
|
+
throw new Error(`Database migration ${missingBeforeApplied.version} is missing before a later migration.`);
|
|
68
|
+
}
|
|
69
|
+
const record = db.prepare(`INSERT INTO ${migrationsTable} (version, name, applied_at) VALUES (?, ?, ?)`);
|
|
70
|
+
for (const migration of databaseMigrations) {
|
|
71
|
+
if (appliedVersions.has(migration.version))
|
|
72
|
+
continue;
|
|
73
|
+
db.transaction(() => {
|
|
74
|
+
migration.apply(db, context);
|
|
75
|
+
record.run(migration.version, migration.name, new Date().toISOString());
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function tableExists(db, table) {
|
|
80
|
+
return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table));
|
|
81
|
+
}
|
|
82
|
+
function missingColumn(db, table, column) {
|
|
83
|
+
return tableExists(db, table)
|
|
84
|
+
&& !db.prepare("SELECT 1 FROM pragma_table_info(?) WHERE name = ?").get(table, column);
|
|
85
|
+
}
|
|
86
|
+
function applyBaselineMigration(db, context) {
|
|
15
87
|
db.exec(`
|
|
16
88
|
CREATE TABLE IF NOT EXISTS users (
|
|
17
89
|
id TEXT PRIMARY KEY,
|
|
@@ -282,10 +354,10 @@ function migrate(db) {
|
|
|
282
354
|
`);
|
|
283
355
|
const editColumns = db.prepare("PRAGMA table_info(project_edit_segments)").all();
|
|
284
356
|
if (!editColumns.some((column) => column.name === "steps_bytes")) {
|
|
285
|
-
db.
|
|
286
|
-
|
|
357
|
+
db.exec("ALTER TABLE project_edit_segments ADD COLUMN steps_bytes INTEGER NOT NULL DEFAULT 0");
|
|
358
|
+
if (context.backfillEditSegmentBytes) {
|
|
287
359
|
db.exec("UPDATE project_edit_segments SET steps_bytes = LENGTH(CAST(steps_json AS BLOB))");
|
|
288
|
-
}
|
|
360
|
+
}
|
|
289
361
|
}
|
|
290
362
|
const projectColumns = db.prepare("PRAGMA table_info(projects)").all();
|
|
291
363
|
if (!projectColumns.some((column) => column.name === "latexmkrc")) {
|
|
@@ -328,14 +400,23 @@ function migrate(db) {
|
|
|
328
400
|
db.exec("ALTER TABLE citation_library_entries ADD COLUMN revision INTEGER NOT NULL DEFAULT 1");
|
|
329
401
|
}
|
|
330
402
|
db.exec(`
|
|
331
|
-
UPDATE users SET can_create_projects = 1 WHERE role = 'admin';
|
|
332
|
-
UPDATE projects SET last_modified_by = owner_id WHERE last_modified_by IS NULL;
|
|
333
|
-
UPDATE compile_runs SET main_file = COALESCE(
|
|
334
|
-
(SELECT project.main_file FROM projects project WHERE project.id = compile_runs.project_id), ''
|
|
335
|
-
) WHERE main_file = '';
|
|
336
403
|
CREATE INDEX IF NOT EXISTS compile_runs_project_main_created
|
|
337
404
|
ON compile_runs(project_id, main_file, created_at DESC);
|
|
338
|
-
|
|
405
|
+
`);
|
|
406
|
+
if (context.backfillProjectLastModifiedBy) {
|
|
407
|
+
db.exec("UPDATE projects SET last_modified_by = owner_id WHERE last_modified_by IS NULL");
|
|
408
|
+
}
|
|
409
|
+
if (context.backfillAdminCanCreateProjects) {
|
|
410
|
+
db.exec("UPDATE users SET can_create_projects = 1 WHERE role = 'admin'");
|
|
411
|
+
}
|
|
412
|
+
if (context.backfillCompileRunMainFile) {
|
|
413
|
+
db.exec(`UPDATE compile_runs SET main_file = COALESCE(
|
|
414
|
+
(SELECT project.main_file FROM projects project WHERE project.id = compile_runs.project_id), ''
|
|
415
|
+
) WHERE main_file = ''`);
|
|
416
|
+
}
|
|
417
|
+
if (!context.migrateLegacyProjectTags)
|
|
418
|
+
return;
|
|
419
|
+
db.exec(`
|
|
339
420
|
INSERT OR IGNORE INTO tags (id, name, color, created_by, created_at, updated_at)
|
|
340
421
|
SELECT legacy.id, legacy.name, legacy.color, p.owner_id, legacy.created_at, legacy.created_at
|
|
341
422
|
FROM project_tags legacy
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { findLatexBibliographyFiles, findLatexSourceIncludes } from "../shared/
|
|
2
|
+
import { findLatexBibliographyFiles, findLatexSourceIncludes } from "../shared/latexDependencies.js";
|
|
3
3
|
/**
|
|
4
4
|
* Extract only the small amount of source information needed for project-level
|
|
5
5
|
* document graph lookups. Keeping this separate from completion symbols means
|
|
@@ -7,29 +7,39 @@ import { findLatexBibliographyFiles, findLatexSourceIncludes } from "../shared/l
|
|
|
7
7
|
*/
|
|
8
8
|
export function latexDocumentDirectives(source) {
|
|
9
9
|
return {
|
|
10
|
-
includes: findLatexSourceIncludes(source).map(
|
|
10
|
+
includes: findLatexSourceIncludes(source).map(toLatexIncludeDirective),
|
|
11
11
|
bibliographies: findLatexBibliographyFiles(source).map((reference) => reference.path)
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
export function toLatexIncludeDirective(reference) {
|
|
15
|
+
return {
|
|
16
|
+
path: reference.path,
|
|
17
|
+
command: reference.command,
|
|
18
|
+
...(reference.directory === undefined ? {} : { directory: reference.directory })
|
|
19
|
+
};
|
|
20
|
+
}
|
|
14
21
|
/** Return the selected root followed by its reachable \input-style sources. */
|
|
15
22
|
export function documentSourceOrder(sources, mainFile) {
|
|
16
23
|
if (!mainFile || !sources.has(mainFile))
|
|
17
24
|
return [];
|
|
18
25
|
const result = [];
|
|
19
|
-
const queued = [mainFile];
|
|
26
|
+
const queued = [{ path: mainFile, importBase: "" }];
|
|
27
|
+
// A source can technically be reached under more than one import context.
|
|
28
|
+
// Keeping its first discovered context mirrors the ordinary project graph
|
|
29
|
+
// traversal while bounding recursive import-package structures.
|
|
20
30
|
const seen = new Set();
|
|
21
31
|
while (queued.length) {
|
|
22
32
|
const current = queued.shift();
|
|
23
|
-
if (!current || seen.has(current))
|
|
33
|
+
if (!current || seen.has(current.path))
|
|
24
34
|
continue;
|
|
25
|
-
const directives = sources.get(current);
|
|
35
|
+
const directives = sources.get(current.path);
|
|
26
36
|
if (!directives)
|
|
27
37
|
continue;
|
|
28
|
-
seen.add(current);
|
|
29
|
-
result.push(current);
|
|
38
|
+
seen.add(current.path);
|
|
39
|
+
result.push(current.path);
|
|
30
40
|
for (const include of directives.includes) {
|
|
31
|
-
const resolved =
|
|
32
|
-
if (resolved && !seen.has(resolved))
|
|
41
|
+
const resolved = resolveLatexInclude(include, current.importBase, sources);
|
|
42
|
+
if (resolved && !seen.has(resolved.path))
|
|
33
43
|
queued.push(resolved);
|
|
34
44
|
}
|
|
35
45
|
}
|
|
@@ -50,25 +60,107 @@ export function declaredBibliographyPaths(sources, mainFile, bibPaths) {
|
|
|
50
60
|
if (!directives)
|
|
51
61
|
continue;
|
|
52
62
|
for (const bibliography of directives.bibliographies) {
|
|
53
|
-
const resolved = resolveProjectReferencePath(bibliography, sourcePath, ".bib", availableBibliographies,
|
|
63
|
+
const resolved = resolveProjectReferencePath(bibliography, sourcePath, ".bib", availableBibliographies, "project-root-then-source-file");
|
|
54
64
|
if (resolved)
|
|
55
65
|
declared.push(resolved);
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
return uniqueProjectPaths(declared);
|
|
59
69
|
}
|
|
60
|
-
export function resolveProjectReferencePath(rawPath, currentPath, extension, available,
|
|
70
|
+
export function resolveProjectReferencePath(rawPath, currentPath, extension, available, resolution) {
|
|
61
71
|
const raw = normalizeReferenceInput(rawPath);
|
|
62
72
|
if (!raw)
|
|
63
73
|
return null;
|
|
64
74
|
const parent = path.posix.dirname(currentPath);
|
|
65
|
-
// TeX resolves relative files against the file that declared them. A raw
|
|
66
|
-
// ../ reference is valid when this final candidate remains inside the
|
|
67
|
-
// project tree (for example chapters/intro.tex -> ../refs.bib).
|
|
68
75
|
const relative = normalizeProjectPath(parent === "." ? raw : path.posix.join(parent, raw));
|
|
69
76
|
const root = normalizeProjectPath(raw);
|
|
70
|
-
const roots =
|
|
71
|
-
|
|
77
|
+
const roots = resolution === "project-root"
|
|
78
|
+
? [root]
|
|
79
|
+
: resolution === "source-file"
|
|
80
|
+
? [relative]
|
|
81
|
+
: [root, relative];
|
|
82
|
+
return resolveCandidatePaths(roots, extension, available);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve import.sty's dynamic path handling. `\\import`, `\\inputfrom`,
|
|
86
|
+
* and `\\includefrom` reset the import path from the compiler root. Their
|
|
87
|
+
* `sub*` counterparts extend the active path. Plain input-style commands use
|
|
88
|
+
* the active path first, then the compiler root, matching import.sty's
|
|
89
|
+
* `\\input@path` fallback behavior.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveLatexInclude(include, importBase, available) {
|
|
92
|
+
switch (include.command.toLowerCase()) {
|
|
93
|
+
case "input":
|
|
94
|
+
case "include": {
|
|
95
|
+
const resolved = resolveOrdinaryIncludePath(include.path, importBase, available);
|
|
96
|
+
return resolved ? { path: resolved, importBase } : null;
|
|
97
|
+
}
|
|
98
|
+
case "subfile":
|
|
99
|
+
return resolveSubfilePath(include.path, importBase, available);
|
|
100
|
+
case "import":
|
|
101
|
+
case "includefrom":
|
|
102
|
+
case "inputfrom": {
|
|
103
|
+
const nextImportBase = normalizeImportBase(include.directory ?? "");
|
|
104
|
+
if (nextImportBase === null)
|
|
105
|
+
return null;
|
|
106
|
+
const resolved = resolveProjectPathAtBase(include.path, "", ".tex", available);
|
|
107
|
+
return resolved ? { path: resolved, importBase: nextImportBase } : null;
|
|
108
|
+
}
|
|
109
|
+
case "subimport":
|
|
110
|
+
case "subinputfrom":
|
|
111
|
+
case "subincludefrom": {
|
|
112
|
+
const nextImportBase = extendImportBase(importBase, include.directory ?? "");
|
|
113
|
+
if (nextImportBase === null)
|
|
114
|
+
return null;
|
|
115
|
+
const resolved = resolveProjectPathAtBase(include.path, importBase, ".tex", available);
|
|
116
|
+
return resolved ? { path: resolved, importBase: nextImportBase } : null;
|
|
117
|
+
}
|
|
118
|
+
default:
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** `subfiles` implements \subfile through import.sty's \subimport. */
|
|
123
|
+
function resolveSubfilePath(rawPath, importBase, available) {
|
|
124
|
+
const raw = normalizeReferenceInput(rawPath);
|
|
125
|
+
if (!raw)
|
|
126
|
+
return null;
|
|
127
|
+
const directory = path.posix.dirname(raw);
|
|
128
|
+
const file = path.posix.basename(raw);
|
|
129
|
+
if (!file || file === "." || file === "/")
|
|
130
|
+
return null;
|
|
131
|
+
const nextImportBase = extendImportBase(importBase, directory === "." ? "" : directory);
|
|
132
|
+
if (nextImportBase === null)
|
|
133
|
+
return null;
|
|
134
|
+
const resolved = resolveProjectPathAtBase(file, nextImportBase, ".tex", available);
|
|
135
|
+
return resolved ? { path: resolved, importBase: nextImportBase } : null;
|
|
136
|
+
}
|
|
137
|
+
function resolveOrdinaryIncludePath(rawPath, importBase, available) {
|
|
138
|
+
if (importBase) {
|
|
139
|
+
const fromImportPath = resolveProjectPathAtBase(rawPath, importBase, ".tex", available);
|
|
140
|
+
if (fromImportPath)
|
|
141
|
+
return fromImportPath;
|
|
142
|
+
}
|
|
143
|
+
return resolveProjectPathAtBase(rawPath, "", ".tex", available);
|
|
144
|
+
}
|
|
145
|
+
function resolveProjectPathAtBase(rawPath, base, extension, available) {
|
|
146
|
+
const raw = normalizeReferenceInput(rawPath);
|
|
147
|
+
const normalizedBase = normalizeImportBase(base);
|
|
148
|
+
if (!raw || normalizedBase === null)
|
|
149
|
+
return null;
|
|
150
|
+
const candidate = normalizeProjectPath(normalizedBase ? path.posix.join(normalizedBase, raw) : raw);
|
|
151
|
+
return resolveCandidatePaths(candidate ? [candidate] : [], extension, available);
|
|
152
|
+
}
|
|
153
|
+
function extendImportBase(importBase, extension) {
|
|
154
|
+
const normalizedBase = normalizeImportBase(importBase);
|
|
155
|
+
const relativeExtension = normalizeRelativeImportPath(extension);
|
|
156
|
+
if (normalizedBase === null || relativeExtension === null)
|
|
157
|
+
return null;
|
|
158
|
+
return normalizeImportBase(normalizedBase
|
|
159
|
+
? path.posix.join(normalizedBase, relativeExtension)
|
|
160
|
+
: relativeExtension);
|
|
161
|
+
}
|
|
162
|
+
function resolveCandidatePaths(paths, extension, available) {
|
|
163
|
+
const candidates = paths.flatMap((candidate) => {
|
|
72
164
|
if (!candidate)
|
|
73
165
|
return [];
|
|
74
166
|
return candidate.toLowerCase().endsWith(extension)
|
|
@@ -96,6 +188,25 @@ function normalizeReferenceInput(value) {
|
|
|
96
188
|
return null;
|
|
97
189
|
return trimmed;
|
|
98
190
|
}
|
|
191
|
+
function normalizeRelativeImportPath(value) {
|
|
192
|
+
const trimmed = value.trim().replaceAll("\\", "/");
|
|
193
|
+
if (trimmed.startsWith("/") || /^[A-Za-z]:\//.test(trimmed) || trimmed.includes("\0"))
|
|
194
|
+
return null;
|
|
195
|
+
return trimmed || ".";
|
|
196
|
+
}
|
|
197
|
+
function normalizeImportBase(value) {
|
|
198
|
+
const relative = normalizeRelativeImportPath(value);
|
|
199
|
+
if (relative === null)
|
|
200
|
+
return null;
|
|
201
|
+
const normalized = path.posix.normalize(relative);
|
|
202
|
+
if (normalized === ".")
|
|
203
|
+
return "";
|
|
204
|
+
if (normalized === ".." || normalized.startsWith("../"))
|
|
205
|
+
return null;
|
|
206
|
+
if (normalized.split("/").some((segment) => segment.toLowerCase() === ".git"))
|
|
207
|
+
return null;
|
|
208
|
+
return normalized;
|
|
209
|
+
}
|
|
99
210
|
function normalizeProjectPath(value) {
|
|
100
211
|
const normalized = path.posix.normalize(value);
|
|
101
212
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../"))
|