gitlatex 1.0.8__tar.gz → 1.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlatex
3
- Version: 1.0.8
3
+ Version: 1.2.0
4
4
  Summary: A simple UI for LaTeX projects with Git support. Clone repos, edit, compile, and push.
5
5
  License-Expression: ISC
6
6
  Project-URL: Homepage, https://github.com/abdussamiakanda/gitlatex
@@ -1,2 +1,2 @@
1
1
  """GitLaTeX IDE - LaTeX projects with Git support."""
2
- __version__ = "1.0.8"
2
+ __version__ = "1.2.0"
@@ -107,7 +107,7 @@ function applyTheme(theme) {
107
107
 
108
108
  function getMonacoTheme() {
109
109
  const t = document.documentElement.getAttribute("data-theme");
110
- return t === "light" ? "vs" : "vs-dark";
110
+ return t === "light" ? "gitlatex-light" : "gitlatex-dark";
111
111
  }
112
112
 
113
113
  function setTheme(theme) {
@@ -183,6 +183,66 @@ function setInfoLink(el, url, label) {
183
183
  }
184
184
  }
185
185
 
186
+ // ----- Update check (PyPI) -----
187
+ let updateInfo = null;
188
+
189
+ /** Ask the server whether a newer gitlatex is on PyPI. Never throws. */
190
+ async function checkForUpdate(force) {
191
+ try {
192
+ const res = await fetchApi("/api/update-check" + (force ? "?force=1" : ""));
193
+ const data = await res.json();
194
+ updateInfo = data && typeof data === "object" ? data : null;
195
+ } catch (_) {
196
+ updateInfo = null;
197
+ }
198
+ renderUpdateState();
199
+ return updateInfo;
200
+ }
201
+
202
+ /** Dot on every Settings gear, plus the notice inside Settings itself. */
203
+ function renderUpdateState() {
204
+ const available = !!(updateInfo && updateInfo.updateAvailable && updateInfo.latest);
205
+ document.querySelectorAll('.icon-btn[href="#/settings"]').forEach(function (el) {
206
+ el.classList.toggle("has-update", available);
207
+ if (available) el.title = "Settings — version " + updateInfo.latest + " available";
208
+ else el.title = "Settings";
209
+ });
210
+
211
+ const notice = document.getElementById("update-notice");
212
+ if (notice) {
213
+ notice.classList.toggle("hidden", !available);
214
+ if (available) {
215
+ const latestEl = document.getElementById("update-notice-latest");
216
+ const currentEl = document.getElementById("update-notice-current");
217
+ const linkEl = document.getElementById("update-notice-link");
218
+ if (latestEl) latestEl.textContent = updateInfo.latest;
219
+ if (currentEl) currentEl.textContent = updateInfo.current || "—";
220
+ if (linkEl && updateInfo.pypi) linkEl.href = updateInfo.pypi;
221
+ }
222
+ }
223
+
224
+ const versionEl = document.getElementById("settings-info-version");
225
+ if (versionEl) {
226
+ const pill = versionEl.querySelector(".version-pill");
227
+ if (pill) pill.remove();
228
+ if (available) {
229
+ const el = document.createElement("span");
230
+ el.className = "version-pill";
231
+ el.textContent = "update available";
232
+ versionEl.appendChild(el);
233
+ }
234
+ }
235
+
236
+ const status = document.getElementById("update-status");
237
+ if (status) {
238
+ if (available || !updateInfo) status.textContent = "";
239
+ else if (updateInfo.disabled) status.textContent = "Update checks are disabled.";
240
+ else if (updateInfo.error) status.textContent = "Could not reach PyPI to check for updates.";
241
+ else if (updateInfo.latest) status.textContent = "You are on the latest version.";
242
+ else status.textContent = "";
243
+ }
244
+ }
245
+
186
246
  async function populateSettingsTechInfo() {
187
247
  const versionEl = document.getElementById("settings-info-version");
188
248
  const urlEl = document.getElementById("settings-info-url");
@@ -200,6 +260,9 @@ async function populateSettingsTechInfo() {
200
260
  if (repoEl) repoEl.textContent = "—";
201
261
  if (pypiEl) pypiEl.textContent = "—";
202
262
  }
263
+ // Re-apply first (setting version wipes the pill), then refresh from PyPI.
264
+ renderUpdateState();
265
+ checkForUpdate();
203
266
  }
204
267
 
205
268
  function setConsole(text) {
@@ -332,6 +395,7 @@ function getRoute() {
332
395
  const parts = hash.split("/").filter(Boolean);
333
396
  if (parts[0] === "editor" && parts[1]) return { page: "editor", repo: parts[1] };
334
397
  if (parts[0] === "settings") return { page: "settings" };
398
+ if (parts[0] === "compiler-api") return { page: "compiler-api" };
335
399
  return { page: "home" };
336
400
  }
337
401
 
@@ -354,6 +418,12 @@ function route() {
354
418
  populateSettings();
355
419
  return;
356
420
  }
421
+ if (r.page === "compiler-api") {
422
+ showView("compiler-api-view");
423
+ const content = document.querySelector("#compiler-api-view .settings-content");
424
+ if (content) content.scrollTop = 0;
425
+ return;
426
+ }
357
427
  showView("editor-view");
358
428
  document.getElementById("editor-repo-name").textContent = decodeURIComponent(r.repo);
359
429
  openEditorPage(r.repo);
@@ -619,9 +689,108 @@ function ensureMonacoReady(callback) {
619
689
  monacoApi = monaco;
620
690
  // Register LaTeX so completion provider and options apply
621
691
  monaco.languages.register({ id: "latex" });
622
- // Word pattern so that "\" and "\section" are "words" for suggest filtering (prefix matches our filterText "\\" + label)
623
692
  monaco.languages.setLanguageConfiguration("latex", {
624
- wordPattern: /\\[a-zA-Z*]*/
693
+ wordPattern: /\\[a-zA-Z*]*/,
694
+ brackets: [["{", "}"], ["[", "]"], ["\\begin{", "\\end{"], ["\\left(", "\\right)"], ["\\left[", "\\right]"], ["\\left\\{", "\\right\\}"]],
695
+ autoClosingPairs: [{ open: "{", close: "}" }, { open: "[", close: "]" }, { open: "(", close: ")" }, { open: "$", close: "$" }]
696
+ });
697
+ monaco.languages.setMonarchTokensProvider("latex", {
698
+ tokenizer: {
699
+ root: [
700
+ [/%[^\n]*/, "comment"],
701
+ [/\\begin\{[^}]*\}/, { token: "keyword", next: "@texBody" }],
702
+ [/\\end\{[^}]*\}/, { token: "keyword" }],
703
+ [/\\(usepackage|documentclass|include|input|bibliography|bibliographystyle|usepackage)(\{[^}]*\})?/, "keyword"],
704
+ [/\\(section|subsection|subsubsection|chapter|paragraph|subparagraph|part)\*?/, "keyword.strong"],
705
+ [/\\(textbf|textit|texttt|textsf|textsc|emph|underline|textmd|textrm|textup|textnormal|textsl|textbf)\{/, "keyword", "@texArgBold"],
706
+ [/\\(newcommand|renewcommand|providecommand|DeclareMathOperator|def|let|newenvironment|renewenvironment)/, "keyword"],
707
+ [/\\(caption|label|ref|pageref|eqref|autoref|cite|citep|citet|nocite|footnote|thanks|maketitle|tableofcontents|listoffigures|listoftables|appendix|addcontentsline|addtocontents|clearpage|newpage|pagebreak|linebreak|nolinebreak|newline|today|TeX|LaTeX|LaTeXe)/, "keyword"],
708
+ [/\\(begin|end|item|hspace|vspace|hfill|vfill|centering|raggedright|raggedleft|noindent|indent|par|smallskip|medskip|bigskip)/, "keyword"],
709
+ [/\\(title|author|date|address|email|institute|thanks|and)/, "keyword"],
710
+ [/\\(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega|varepsilon|vartheta|varpi|varrho|varsigma|varphi)/, "type"],
711
+ [/\\(int|sum|prod|oint|coprod|bigcup|bigcap|bigvee|bigwedge|bigoplus|bigotimes|bigodot|biguplus|lim|log|ln|sin|cos|tan|cot|sec|csc|arcsin|arccos|arctan|sinh|cosh|tanh|coth|det|dim|gcd|hom|inf|ker|lg|max|min|Pr|sup|exp|deg|mod|bmod|pmod|pm|mp|times|div|ast|star|circ|bullet|cdot|cap|cup|uplus|sqcap|sqcup|vee|wedge|setminus|wr|diamond|bigtriangleup|bigtriangledown|triangleleft|triangleright|lhd|rhd|unlhd|unrhd|oplus|ominus|otimes|oslash|odot|bigcirc|dagger|ddagger|amalg|leq|geq|neq|equiv|approx|cong|propto|sim|simeq|asymp|doteq|parallel|models|bot|mid|prec|succ|preceq|succeq|ll|gg|subset|supset|subseteq|supseteq|sqsubset|sqsupseteq|in|ni|notin|vdash|dashv|smile|frown|perp|bowtie|Join|propto|mapsto|to|gets|leftarrow|rightarrow|uparrow|downarrow|updownarrow|Leftarrow|Rightarrow|Uparrow|Downarrow|Updownarrow|longleftarrow|longrightarrow|longleftrightarrow|Longleftarrow|Longrightarrow|Longleftrightarrow|nearrow|searrow|swarrow|nwarrow|leftharpoonup|leftharpoondown|rightharpoonup|rightharpoondown|rightleftharpoons|leadsto|iff)/, "type"],
712
+ [/\\[a-zA-Z@*]+/, "keyword"],
713
+ [/\$\$/, { token: "delimiter.math", next: "@mathDisplay" }],
714
+ [/\$/, { token: "delimiter.math", next: "@mathInline" }],
715
+ [/[{}]/, "delimiter.bracket"]
716
+ ],
717
+ texBody: [
718
+ [/%[^\n]*/, "comment"],
719
+ [/\\end\{[^}]*\}/, { token: "keyword", next: "@pop" }],
720
+ [/\\[a-zA-Z@*]+/, "keyword"],
721
+ [/[{}]/, "delimiter.bracket"],
722
+ [/\$\$/, { token: "delimiter.math", next: "@mathDisplay" }],
723
+ [/\$/, { token: "delimiter.math", next: "@mathInline" }]
724
+ ],
725
+ texArgBold: [
726
+ [/[^}]+/, "string"],
727
+ [/\}/, "delimiter.bracket", "@pop"]
728
+ ],
729
+ mathDisplay: [
730
+ [/\$\$/, { token: "delimiter.math", next: "@pop" }],
731
+ [/\\[a-zA-Z@*]+/, "type"],
732
+ [/[{}]/, "delimiter.bracket"],
733
+ [/[0-9]+/, "number"],
734
+ [/[_^]/, "operator"],
735
+ [/\w+/, "identifier"]
736
+ ],
737
+ mathInline: [
738
+ [/\$/, { token: "delimiter.math", next: "@pop" }],
739
+ [/\\[a-zA-Z@*]+/, "type"],
740
+ [/[{}]/, "delimiter.bracket"],
741
+ [/[0-9]+/, "number"],
742
+ [/[_^]/, "operator"],
743
+ [/\w+/, "identifier"]
744
+ ]
745
+ }
746
+ });
747
+
748
+ // === Editor color themes so LaTeX + BibTeX tokens are visible ===
749
+ monaco.editor.defineTheme("gitlatex-dark", {
750
+ base: "vs-dark",
751
+ inherit: true,
752
+ rules: [
753
+ { token: "comment", foreground: "6a9955", fontStyle: "italic" },
754
+ { token: "keyword", foreground: "569cd6" },
755
+ { token: "keyword.strong", foreground: "4ec9b0", fontStyle: "bold" },
756
+ { token: "type", foreground: "c586c0" },
757
+ { token: "string", foreground: "ce9178" },
758
+ { token: "number", foreground: "b5cea8" },
759
+ { token: "delimiter.bracket", foreground: "808080" },
760
+ { token: "delimiter.math", foreground: "e8ab53", fontStyle: "bold" },
761
+ { token: "operator", foreground: "d4d4d4" },
762
+ { token: "identifier", foreground: "9cdcfe" }
763
+ ],
764
+ colors: {
765
+ "editor.background": "#0d1117",
766
+ "editor.foreground": "#e6edf3",
767
+ "editorCursor.foreground": "#e6edf3",
768
+ "editorLineNumber.foreground": "#484f58",
769
+ "editorLineNumber.activeForeground": "#8b949e"
770
+ }
771
+ });
772
+ monaco.editor.defineTheme("gitlatex-light", {
773
+ base: "vs",
774
+ inherit: true,
775
+ rules: [
776
+ { token: "comment", foreground: "6a9955", fontStyle: "italic" },
777
+ { token: "keyword", foreground: "0000ff" },
778
+ { token: "keyword.strong", foreground: "795e26", fontStyle: "bold" },
779
+ { token: "type", foreground: "af00db" },
780
+ { token: "string", foreground: "a31515" },
781
+ { token: "number", foreground: "098658" },
782
+ { token: "delimiter.bracket", foreground: "808080" },
783
+ { token: "delimiter.math", foreground: "d16969", fontStyle: "bold" },
784
+ { token: "operator", foreground: "000000" },
785
+ { token: "identifier", foreground: "001080" }
786
+ ],
787
+ colors: {
788
+ "editor.background": "#ffffff",
789
+ "editor.foreground": "#1f2328",
790
+ "editorCursor.foreground": "#1f2328",
791
+ "editorLineNumber.foreground": "#8c959f",
792
+ "editorLineNumber.activeForeground": "#656d76"
793
+ }
625
794
  });
626
795
 
627
796
  // Register BibTeX for .bib files
@@ -1138,7 +1307,7 @@ async function loadFile(path) {
1138
1307
  if (monacoApi) {
1139
1308
  const model = editor.getModel();
1140
1309
  if (model) {
1141
- const lang = path.endsWith(".tex") ? "latex" : path.endsWith(".bib") ? "bib" : "plaintext";
1310
+ const lang = (path.endsWith(".tex") || path.endsWith(".sty") || path.endsWith(".cls")) ? "latex" : path.endsWith(".bib") ? "bib" : "plaintext";
1142
1311
  monacoApi.editor.setModelLanguage(model, lang);
1143
1312
  }
1144
1313
  }
@@ -1790,6 +1959,25 @@ document.addEventListener("click", function (e) {
1790
1959
  }
1791
1960
  });
1792
1961
 
1962
+ // Copy buttons on the Compiler API docs code samples
1963
+ document.addEventListener("click", function (e) {
1964
+ const btn = e.target.closest(".docs-copy");
1965
+ if (!btn) return;
1966
+ const code = btn.parentElement && btn.parentElement.querySelector("code");
1967
+ if (!code || !navigator.clipboard) return;
1968
+ navigator.clipboard.writeText(code.textContent).then(function () {
1969
+ btn.textContent = "Copied";
1970
+ btn.classList.add("copied");
1971
+ setTimeout(function () {
1972
+ btn.textContent = "Copy";
1973
+ btn.classList.remove("copied");
1974
+ }, 1500);
1975
+ }).catch(function () {
1976
+ btn.textContent = "Press Ctrl+C";
1977
+ setTimeout(function () { btn.textContent = "Copy"; }, 1500);
1978
+ });
1979
+ });
1980
+
1793
1981
  document.getElementById("settings-save")?.addEventListener("click", function () {
1794
1982
  const apiInput = document.getElementById("settings-compiler-api");
1795
1983
  const keyInput = document.getElementById("settings-compiler-api-key");
@@ -1889,4 +2077,5 @@ window.addEventListener("hashchange", route);
1889
2077
  window.addEventListener("load", function () {
1890
2078
  route();
1891
2079
  setupResizers();
2080
+ checkForUpdate();
1892
2081
  });