From 9043f3015e334c5647c5fa27d6eba34744bacaa2 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 26 Sep 2024 18:01:33 -0700 Subject: [PATCH] UX improvements (#243) Co-authored-by: Quint Fain Co-authored-by: Cbarr-hub Co-authored-by: Jaden McElvey Co-authored-by: Jefferson <44975876+ZX-80@users.noreply.github.com> Co-authored-by: darklordsep1 Co-authored-by: Roni Ruadap Co-authored-by: Akshat Saini --- README.md | 59 ++- jupyterlab_latex/build.py | 86 +++- package.json | 1 + sample.tex | 16 +- src/error.tsx | 63 ++- src/index.ts | 716 ++++++++++++++++++++++++++++- style/icons/bold.svg | 1 + style/icons/center-align.svg | 1 + style/icons/chart-column-solid.svg | 1 + style/icons/italic.svg | 1 + style/icons/left-align.svg | 1 + style/icons/list.svg | 1 + style/icons/olist.svg | 1 + style/icons/right-align.svg | 1 + style/icons/table.svg | 1 + style/icons/underline.svg | 1 + yarn.lock | 250 +++++++--- 17 files changed, 1107 insertions(+), 94 deletions(-) create mode 100644 style/icons/bold.svg create mode 100644 style/icons/center-align.svg create mode 100644 style/icons/chart-column-solid.svg create mode 100644 style/icons/italic.svg create mode 100644 style/icons/left-align.svg create mode 100644 style/icons/list.svg create mode 100644 style/icons/olist.svg create mode 100644 style/icons/right-align.svg create mode 100644 style/icons/table.svg create mode 100644 style/icons/underline.svg diff --git a/README.md b/README.md index 13fc506..9a413ec 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,59 @@ A JupyterLab extension for live-editing of LaTeX documents. ## Usage -To use, right-click on an open `.tex` document within JupyterLab, and select `Show LaTeX Preview`: -![preview](images/show_preview.png) -This will compile the `.tex` file and open the rendered PDF document. -Subsequent saves of the file will automatically update the PDF. -If the PDF fails to compile (possibly due to a syntax error), -an error panel will open detailing the LaTeX error. +### Compilation + +To compile and preview a LaTeX document: + +1. Open a `.tex` document within JupyterLab. +2. Use one of the following methods to compile and preview the document: + - Right-click on the document and select `Show LaTeX Preview` from the context menu. + - Click the `Preview` button in the toolbar at the top of the document. + +Both methods will compile the `.tex` file and open the rendered PDF document. Subsequent saves of the file will automatically update the PDF. If the PDF fails to compile (possibly due to a syntax error), an error panel will open detailing the LaTeX error. + +### Writing Tools + +A toolbar menu at the top of the document provides shortcuts to common LaTeX editing tasks: + +#### Text Formatting + +- **Subscript**: Highlight the text you want to subscript and click the `Xᵧ` button. If no text is highlighted, an input dialog will appear for you to enter the subscript. +- **Superscript**: Highlight the text you want to superscript and click the `Xⁿ` button. If no text is highlighted, an input dialog will appear for you to enter the superscript. +- **Bold**: Highlight the text you want to format in bold and click the `B` button. +- **Italic**: Highlight the text you want to format in italics and click the `I` button. +- **Underline**: Highlight the text you want to underline and click the `U` button. + +#### Text Layout + +- **Left Align**: Highlight the text you want to align left and click the left alignment button. +- **Center Align**: Highlight the text you want to center align and click the center alignment button. +- **Right Align**: Highlight the text you want to align right and click the right alignment button. + +#### Lists + +- **Bullet List**: Click the bullet list button to insert a bullet list. +- **Numbered List**: Click the numbered list button to insert a numbered list. + +#### Tables and Plots + +- **Table Creation GUI**: Click the table button to open a dialog for creating a table with a specified number of rows and columns. +- **Add Plot**: Click the plot button to select a plot type and insert it into your document. Available plot types include: + - Simple function plot + - Plot from file + - Scatter plot + - Bar graphs + - Contour plot + - Parametric plot + +### Error Handling + +- **Error Log Filtering Options**: Enhanced error log filtering options to help you quickly identify and resolve issues. + +### Main Menu Helpers + +- **Constant Menu**: Quickly insert common mathematical constants. +- **Symbol Menu**: Easily insert various mathematical symbols. For more advanced usage documentation, see [here](docs/advanced.md). diff --git a/jupyterlab_latex/build.py b/jupyterlab_latex/build.py index 2013ec4..2d2f69c 100644 --- a/jupyterlab_latex/build.py +++ b/jupyterlab_latex/build.py @@ -160,6 +160,89 @@ def bib_condition(self): """ return any([re.match(r'.*\.bib', x) for x in set(glob.glob("*"))]) + def filter_output(self, latex_output): + """Filters latex output for "interesting" messages + + Parameters + ---------- + latex_output: string + This is the output of the executed latex command from, + run_command in run_latex. + + returns: + A string representing the filtered output. + + Notes + ----- + - Based on the public domain perl script texfot v 1.43 written by + Karl Berry in 2014. It has no home page beyond the package on + CTAN: . + + """ + ignore = re.compile(r'''^( + LaTeX\ Warning:\ You\ have\ requested\ package + |LaTeX\ Font\ Warning:\ Some\ font\ shapes + |LaTeX\ Font\ Warning:\ Size\ substitutions + |Package\ auxhook\ Warning:\ Cannot\ patch + |Package\ caption\ Warning:\ Un(supported|known)\ document\ class + |Package\ fixltx2e\ Warning:\ fixltx2e\ is\ not\ required + |Package\ frenchb?\.ldf\ Warning:\ (Figures|The\ definition) + |Package\ layouts\ Warning:\ Layout\ scale + |\*\*\*\ Reloading\ Xunicode\ for\ encoding # spurious *** + |pdfTeX\ warning:.*inclusion:\ fou #nd PDF version ... + |pdfTeX\ warning:.*inclusion:\ mul #tiple pdfs with page group + |libpng\ warning:\ iCCP:\ Not\ recognizing + |!\ $ + |This\ is + |No\ pages\ of\ output. # possibly not worth ignoring? + )''', re.VERBOSE) + + next_line = re.compile(r'''^( + .*?:[0-9]+: # usual file:lineno: form + |! # usual ! form + |>\ [^<] # from \show..., but not "> ... + |Missing\ character: # good to show (need \tracinglostchars=1) + |\\endL.*problem # XeTeX? + |\*\*\*\s # *** from some packages or subprograms + |l\.[0-9]+ # line number marking + |all\ text\ was\ ignored\ after\ line + |.*Fatal\ error + |.*for\ symbol.*on\ input\ line + )''', re.VERBOSE) + + print_next = False + filtered_output = [] + + for line in latex_output.split('\n'): + if print_next: + filtered_output.append(line) + print_next = False + elif ignore.match(line): + continue + + elif next_line.match(line): + filtered_output.append(line) + print_next = True + + elif show.match(line): + filtered_output.append(line) + + return '\n'.join(filtered_output) @gen.coroutine def run_latex(self, command_sequence): @@ -193,7 +276,7 @@ def run_latex(self, command_sequence): self.set_status(500) self.log.error((f'LaTeX command `{" ".join(cmd)}` ' f'errored with code: {code}')) - return output + return json.dumps({'fullMessage':output, 'errorOnlyMessage':self.filter_output(output)}) return "LaTeX compiled" @@ -229,4 +312,3 @@ def get(self, path = ''): cmd_sequence = self.build_tex_cmd_sequence(tex_base_name) out = yield self.run_latex(cmd_sequence) self.finish(out) - diff --git a/package.json b/package.json index 02b4511..8c3aab2 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@jupyterlab/fileeditor": "^4.0.0", "@jupyterlab/launcher": "^4.0.0", "@jupyterlab/mainmenu": "^4.0.0", + "@jupyterlab/notebook": "^4.0.0", "@jupyterlab/services": "^7.0.0", "@jupyterlab/settingregistry": "^4.0.0", "@jupyterlab/statedb": "^4.0.0", diff --git a/sample.tex b/sample.tex index 2e74d75..227bef6 100644 --- a/sample.tex +++ b/sample.tex @@ -6,17 +6,18 @@ \title{JupyterLab \LaTeX} \date{} \maketitle - +test +$5_{3_{4} + 4_{3} + 5^{4}}$ \section{Introduction} This is a sample document demonstrating the ability to live-edit \LaTeX documents in JupyterLab. \\ -\\ Right-click on this document, and select "Show LaTeX Preview". A new panel should open up on the right with the PDF that is generated by this document. If there are any errors in the document, an error panel should open below. \\ +Test \\ We can write equations: \begin{equation} @@ -25,7 +26,6 @@ \section{Introduction} \label{eq:Navier–Stokes} \end{equation} \\ -\\ And we can write tables: \begin{center} \begin{tabular}{ | l | c | r| } @@ -37,8 +37,16 @@ \section{Introduction} \end{tabular} \end{center} + +\centering{Right-click on this document, and select "Show LaTeX Preview". A new panel should open up on the right with the PDF that is generated by this document. If there are any errors in the document, an error panel should open below.} + + We can reference equations by their numbers, i.e. Equation (\ref{eq:Navier–Stokes}). +Hello +We can reference equations by their numbers, i.e. Equation (\ref{eq:Navier–Stokes}). + + \subsection{We can add new subsections} And we can include images, such as the Jupyter logo: \begin{center} @@ -50,8 +58,6 @@ \subsection{We can add new subsections} \pagebreak - - You can also use SyncTeX by typing ⌘(CMD)/CTRL+⇧(SHIFT)+X. \end{document} diff --git a/src/error.tsx b/src/error.tsx index 521c1ab..ecc3c17 100644 --- a/src/error.tsx +++ b/src/error.tsx @@ -5,12 +5,17 @@ import { Message } from '@lumino/messaging'; import { Widget } from '@lumino/widgets'; +import { HTMLSelect } from '@jupyterlab/ui-components'; + import * as React from 'react'; import * as ReactDOM from 'react-dom'; const LATEX_ERROR_PANEL = 'jp-LatexErrorPanel'; const LATEX_ERROR_CONTAINER = 'jp-LatexErrorContainer'; +const TOOLBAR_CELLTYPE_CLASS = 'jp-Notebook-toolbarCellType'; +const TOOLBAR_CELLTYPE_DROPDOWN_CLASS = 'jp-Notebook-toolbarCellTypeDropdown'; + /** * A widget which hosts the error logs from LaTeX * when document compilation fails. @@ -22,10 +27,11 @@ export class ErrorPanel extends Widget { constructor() { super(); this.addClass(LATEX_ERROR_PANEL); + this.addClass(TOOLBAR_CELLTYPE_CLASS); } set text(value: string) { - ReactDOM.render(, this.node, () => { + ReactDOM.render(, this.node, () => { this.update(); }); } @@ -34,7 +40,7 @@ export class ErrorPanel extends Widget { * Handle an update request. */ protected onUpdateRequest(msg: Message): void { - const el = this.node.children[0]; + const el = this.node.children[2].children[0]; el.scrollTop = el.scrollHeight; } @@ -48,14 +54,61 @@ export class ErrorPanel extends Widget { export interface ILatexProps { text: string; + node: ErrorPanel; } export class LatexError extends React.Component { + selectedValue: string | undefined; + fullMessage: string; + errorOnlyMessage: string; + displayedMessage: string; + + constructor(props: Readonly) { + super(props); + let messages = JSON.parse(props.text); + this.fullMessage = messages.fullMessage; + this.errorOnlyMessage = messages.errorOnlyMessage; + this.displayedMessage = this.errorOnlyMessage; + } + + handleChange = (event: React.ChangeEvent): void => { + this.selectedValue = event.target.value; + if (event.target.value === 'Filtered') { + this.displayedMessage = this.errorOnlyMessage; + } else if (event.target.value === 'Unfiltered') { + this.displayedMessage = this.fullMessage; + } else if (event.target.value === 'JSON') { + this.displayedMessage = this.props.text; + } + + /** + * Force ErrorPanel to rerender. + */ + this.setState({}); + this.props.node.update(); + }; + render() { return ( -
-        {this.props.text}
-      
+ <> + + +
+ +
+ +
+
+            {this.displayedMessage}
+          
+
+ ); } } diff --git a/src/index.ts b/src/index.ts index b8b3818..df2bc90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,10 @@ import { import { IWidgetTracker, WidgetTracker, - showErrorMessage + showErrorMessage, + ICommandPalette, + InputDialog, + ToolbarButton } from '@jupyterlab/apputils'; import { CodeEditor } from '@jupyterlab/codeeditor'; @@ -44,14 +47,29 @@ import { LabIcon } from '@jupyterlab/ui-components'; import { IDefaultFileBrowser } from '@jupyterlab/filebrowser'; -import { ICommandPalette } from '@jupyterlab/apputils'; - import { IMainMenu } from '@jupyterlab/mainmenu'; import latexIconStr from '../style/latex.svg'; +import listIconStr from '../style/icons/list.svg'; +import olistIconStr from '../style/icons/olist.svg'; +import italicIconStr from '../style/icons/italic.svg'; +import boldIconStr from '../style/icons/bold.svg'; +import underlineIconStr from '../style/icons/underline.svg'; +import tableIconStr from '../style/icons/table.svg'; +import rightIconStr from '../style/icons/right-align.svg'; +import centerIconStr from '../style/icons/center-align.svg'; +import leftIconStr from '../style/icons/left-align.svg'; +import plotIconStr from '../style/icons/chart-column-solid.svg'; + import '../style/index.css'; +import { Menu } from '@lumino/widgets'; + +import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook'; + +import { IDisposable, DisposableDelegate } from '@lumino/disposable'; + /** * A class that tracks editor widgets. */ @@ -88,6 +106,8 @@ namespace CommandIDs { * Create new latex file */ export const createNew = 'latex:create-new-latex-file'; + + export const createTable = 'latex:create-table'; } /** @@ -214,6 +234,17 @@ function synctexViewRequest( }); } +function isLatexFile( + editorTracker: IEditorTracker +): IDocumentWidget | null { + const widget = editorTracker.currentWidget; + if (widget && PathExt.extname(widget.context.path) === '.tex') { + return widget; + } else { + return null; + } +} + /** * Activate the file browser. */ @@ -391,6 +422,514 @@ function activateLatexPlugin( state.save(id, { paths: Array.from(Private.previews) }); }; + class EditorToolbarPanel + implements DocumentRegistry.IWidgetExtension + { + createNew( + panel: NotebookPanel, + context: DocumentRegistry.IContext + ): IDisposable { + const execOpenLataxPreview = () => { + commands.execute(CommandIDs.openLatexPreview); + }; + + const createInputDialog = (mess: string, action: string) => { + const widget = editorTracker.currentWidget; + if (widget) { + const editor = widget.content.editor; + InputDialog.getText({ + title: mess + }).then(value => { + if (value.value) { + if (editor.replaceSelection) { + editor.replaceSelection(action + '{' + value.value + '}'); + } + } + }); + } + }; + + const replaceSelection = (action: string) => { + const widget = editorTracker.currentWidget; + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection && editor.getSelection) { + const start = editor.getSelection().start; + const end = editor.getSelection().end; + if (start.line === end.line) { + let selection: string | undefined = editor.getLine(start.line); + if (selection) { + selection = selection.substring(start.column, end.column); + if (selection.length > 0) { + editor.replaceSelection(action + '{' + selection + '}'); + return 1; + } + } + } + } + } + return 0; + }; + + const insertSubscript = () => { + const action = '_'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Desired Subscript', action); + } + }; + + const insertSuperscript = () => { + const action = '^'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Desired Superscript', action); + } + }; + + const insertFraction = () => { + InputDialog.getText({ + title: + 'Provide Desired Fraction: Numerator, Denominator\nEX: 1,2 -> \u00BD ' + }).then(value => { + if (value.value) { + const widget = editorTracker.currentWidget; + const inputString = value.value; + const inputArgs = inputString.split(','); + if (widget && inputArgs.length === 2) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection( + '\\frac{' + + inputArgs[0].trim() + + '}{' + + inputArgs[1].trim() + + '}' + ); + } + } + } + }); + }; + + const leftAlign = () => { + const action = '\\leftline'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Left Align', action); + } + }; + + const centerAlign = () => { + const action = '\\centerline'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Center Align', action); + } + }; + + const rightAlign = () => { + const action = '\\rightline'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Right Align', action); + } + }; + + const insertBold = () => { + const action = '\\textbf'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Bold', action); + } + }; + + const insertItalics = () => { + const action = '\\textit'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Italicise', action); + } + }; + + const insertUnderline = () => { + const action = '\\underline'; + const result = replaceSelection(action); + if (result === 0) { + createInputDialog('Provide Text to Underline', action); + } + }; + + const insertBulletList = () => { + const widget = editorTracker.currentWidget; + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection( + '\\begin{itemize}' + + '\n' + + '\t' + + '\\item' + + '\n' + + '\\end{itemize}' + ); + } + } + }; + + const insertNumberedList = () => { + const widget = editorTracker.currentWidget; + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection( + '\\begin{enumerate}' + + '\n' + + '\t' + + '\\item' + + '\n' + + '\\end{enumerate}' + ); + } + } + }; + const insertPlot = () => { + InputDialog.getItem({ + title: 'Select Plot Type', + items: [ + 'Mathematical Expression', + 'Data From File', + 'Scatter Plot', + 'Bar Graphs', + 'Contour Plots', + 'Parametric Plot' + ] + }).then(value => { + if (value.value) { + let plotText = ''; + + switch (value.value) { + case 'Mathematical Expression': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}[' + + '\n\taxis lines = left,' + + '\n\txlabel = \\(x\\),' + + '\n\tylabel = {\\(f(x)\\)},' + + '\n]' + + '\n\\addplot [' + + '\n\tdomain=-10:10, ' + + '\n\tsamples=100, ' + + '\n\tcolor=blue,' + + '\n]' + + '\n{x^2};' + + '\n\\addlegendentry{\\(x^2\\)}' + + '\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + case 'Data From File': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}[' + + '\n\ttitle={Title},' + + '\n\txlabel={x axis label},' + + '\n\tylabel={y axis label},' + + '\n\txmin=0, xmax=100,' + + '\n\tymin=0, ymax=100,' + + '\n\txtick={},' + + '\n\tytick={},' + + '\n\tlegend pos=north west' + + '\n]' + + '\n\n\\addplot[' + + '\n\tcolor=blue,' + + '\n\tmark=*]' + + '\n{Data File Path};' + + '\n\n\\legend{Legend Text}' + + '\n\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + case 'Scatter Plot': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}[' + + '\n\ttitle={Title},' + + '\n\txlabel={x axis label},' + + '\n\tylabel={y axis label},' + + '\n\txmin=0, xmax=100,' + + '\n\tymin=0, ymax=100,' + + '\n\txtick={},' + + '\n\tytick={},' + + '\n\tlegend pos=north west' + + '\n]' + + '\n\n\\addplot[' + + '\n\tonly marks,' + + '\n\tmark=*]' + + '\ntable' + + '\n{Data File Path};' + + '\n\n\\legend{Legend Text}' + + '\n\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + case 'Bar Graphs': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}[' + + '\ntitle={Title},' + + '\nxlabel={x axis label},' + + '\nylabel={y axis label},' + + '\nxmin=0, xmax=100,' + + '\nymin=0, ymax=100,' + + '\nenlargelimits=0.05,' + + '\nlegend pos=north west,' + + '\nybar,' + + '\n]' + + '\n\n\\addplot table {\\mydata};' + + '\n\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + case 'Contour Plots': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}' + + '\n[' + + '\n\ttitle={Title},' + + '\n\tview={0}{90}' + + '\n]' + + '\n\\addplot3[' + + '\n\tcontour gnuplot={levels={0.5}}' + + '\n]' + + '\n{sqrt(x^2+y^2)};' + + '\n\\addlegendentry{\\(sqrt(x^2+y^2)\\)}' + + '\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + case 'Parametric Plot': { + plotText = + '\\begin{tikzpicture}' + + '\n\\begin{axis}' + + '\n[' + + '\n\ttitle={Title},' + + '\n\tview={60}{30}' + + '\n]' + + '\n\n\\addplot3[' + + '\n\tdomain=-5:5,' + + '\n\tsamples = 60,' + + '\n\tsamples y=0,' + + '\n]' + + '\n({sin(deg(x))},' + + '\n{cos(deg(x))},' + + '\n{x});' + + '\n\n\\addlegendentry{\\(Legend Label)\\)}' + + '\n\n\\end{axis}' + + '\n\\end{tikzpicture}'; + break; + } + } + + const widget = editorTracker.currentWidget; + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection(plotText); + } + } + } + }); + }; + + const execCreateTable = () => { + //const createTable = 'latex:create-table' + commands.execute(CommandIDs.createTable); + //app.commands.addCommand('latex:create-table', { + }; + + const previewButton = new ToolbarButton({ + className: 'run-latexPreview-command', + label: 'Preview', + onClick: execOpenLataxPreview, + tooltip: 'Click to preview your LaTeX document' + }); + + const subscriptButton = new ToolbarButton({ + className: 'insert-subscript', + label: 'Xᵧ', + onClick: insertSubscript, + tooltip: 'Click to open subscript input dialog' + }); + + const superscriptButton = new ToolbarButton({ + className: 'insert-superscript', + label: 'X\u02B8', + onClick: insertSuperscript, + tooltip: 'Click to open superscript input dialog' + }); + + const fractionButton = new ToolbarButton({ + className: 'insert-fraction', + label: 'X/Y', + onClick: insertFraction, + tooltip: 'Click to open fraction input dialog' + }); + const lefticon = new LabIcon({ + name: 'launcher:left-icon', + svgstr: leftIconStr + }); + const leftTextAlignmentButton = new ToolbarButton({ + className: 'insert-text', + icon: lefticon, + onClick: leftAlign, + tooltip: 'Click to left align highlighted text' + }); + + const centericon = new LabIcon({ + name: 'launcher:center-icon', + svgstr: centerIconStr + }); + const centerTextAlignmentButton = new ToolbarButton({ + className: 'insert-text', + icon: centericon, + onClick: centerAlign, + tooltip: 'Click to left align highlighted text' + }); + + const righticon = new LabIcon({ + name: 'launcher:right-icon', + svgstr: rightIconStr + }); + const rightTextAlignmentButton = new ToolbarButton({ + className: 'insert-text', + icon: righticon, + onClick: rightAlign, + tooltip: 'Click to left align highlighted text' + }); + const boldicon = new LabIcon({ + name: 'launcher:bold-icon', + svgstr: boldIconStr + }); + + const boldButton = new ToolbarButton({ + className: 'bold-text', + icon: boldicon, + onClick: insertBold, + tooltip: 'Click to insert bold text' + }); + + const italicsicon = new LabIcon({ + name: 'launcher:italics-icon', + svgstr: italicIconStr + }); + + const italicsButton = new ToolbarButton({ + className: 'italicize-text', + icon: italicsicon, + onClick: insertItalics, + tooltip: 'Click to insert italicized text' + }); + + const underlineicon = new LabIcon({ + name: 'launcher:underline-icon', + svgstr: underlineIconStr + }); + const underlineButton = new ToolbarButton({ + className: 'underline-text', + icon: underlineicon, + onClick: insertUnderline, + tooltip: 'Click to insert underlined text' + }); + + const listicon = new LabIcon({ + name: 'launcher:list-icon', + svgstr: listIconStr + }); + + const bulletListButton = new ToolbarButton({ + className: 'insert-bullet-list', + icon: listicon, + onClick: insertBulletList, + tooltip: 'Click to insert bullet list' + }); + + const olisticon = new LabIcon({ + name: 'launcher:olist-icon', + svgstr: olistIconStr + }); + + const numberedListButton = new ToolbarButton({ + className: 'insert-numbered-list', + icon: olisticon, + onClick: insertNumberedList, + tooltip: 'Click to insert numbered list' + }); + + const tableicon = new LabIcon({ + name: 'launcher:table-icon', + svgstr: tableIconStr + }); + const tableInsertButton = new ToolbarButton({ + className: 'insert-table', + icon: tableicon, + onClick: execCreateTable, + tooltip: 'Click to insert table' + }); + + const plotIcon = new LabIcon({ + name: 'launcher:plot-icon', + svgstr: plotIconStr + }); + + const plotButton = new ToolbarButton({ + className: 'insert-plot', + icon: plotIcon, + onClick: insertPlot, + tooltip: 'Click to insert a plot' + }); + + if (context.path.endsWith('.tex')) { + panel.toolbar.insertItem(10, 'Preview', previewButton); + panel.toolbar.insertItem(10, 'sub', subscriptButton); + panel.toolbar.insertItem(10, 'super', superscriptButton); + panel.toolbar.insertItem(10, 'fraction', fractionButton); + + panel.toolbar.insertItem(10, 'left', leftTextAlignmentButton); + panel.toolbar.insertItem(10, 'center', centerTextAlignmentButton); + panel.toolbar.insertItem(10, 'right', rightTextAlignmentButton); + + panel.toolbar.insertItem(10, 'bold', boldButton); + panel.toolbar.insertItem(10, 'italics', italicsButton); + panel.toolbar.insertItem(10, 'underline', underlineButton); + panel.toolbar.insertItem(10, 'bullet-list', bulletListButton); + panel.toolbar.insertItem(10, 'numbered-list', numberedListButton); + panel.toolbar.insertItem(10, 'table', tableInsertButton); + panel.toolbar.insertItem(10, 'insert-plot', plotButton); + } + return new DisposableDelegate(() => { + previewButton.dispose(); + subscriptButton.dispose(); + superscriptButton.dispose(); + fractionButton.dispose(); + + leftTextAlignmentButton.dispose(); + centerTextAlignmentButton.dispose(); + rightTextAlignmentButton.dispose(); + + boldButton.dispose(); + italicsButton.dispose(); + underlineButton.dispose(); + bulletListButton.dispose(); + numberedListButton.dispose(); + tableInsertButton.dispose(); + plotButton.dispose(); + }); + } + } + + app.docRegistry.addWidgetExtension('Editor', new EditorToolbarPanel()); + // If there are any active previews in the statedb, // activate them upon initialization. Promise.all([state.fetch(id), app.restored]).then(([args]) => { @@ -442,10 +981,7 @@ function activateLatexPlugin( }, isEnabled: hasWidget, isVisible: () => { - const widget = editorTracker.currentWidget; - return ( - (widget && PathExt.extname(widget.context.path) === '.tex') || false - ); + return isLatexFile(editorTracker) !== null; }, label: 'Show LaTeX Preview' }); @@ -502,6 +1038,7 @@ function activateLatexPlugin( // Add the command to the menu if (menu) { menu.fileMenu.newMenu.addGroup([{ command }], 30); + addLatexMenu(app, editorTracker, menu); } } @@ -647,6 +1184,171 @@ function addSynctexCommands( return disposables; } +function addLatexMenu( + app: JupyterFrontEnd, + editorTracker: IEditorTracker, + mainMenu: IMainMenu +): void { + const constantMenu = new Menu({ commands: app.commands }); + constantMenu.title.label = 'Constants'; + + const constants = new Map(); + constants.set('Pi', '\\pi'); + constants.set('Euler–Mascheroni constant', '\\gamma'); + constants.set('Golden Ratio', '\\varphi'); + + constants.forEach((value: string, key: string) => { + const commandName = 'latex:' + key.replace(' ', '-').toLowerCase(); + app.commands.addCommand(commandName, { + label: key, + caption: value, + execute: async args => { + const widget = isLatexFile(editorTracker); + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection(value); + } + } + } + }); + + constantMenu.addItem({ + command: commandName, + args: {} + }); + }); + + const symbolMenu = new Menu({ commands: app.commands }); + symbolMenu.title.label = 'Symbols'; + + const symbols = new Map(); + // Less than symbols + symbols.set('Not Less Than', '\\nless'); + symbols.set('Less Than or Equal', '\\leq'); + symbols.set('Not Less Than or Equal', '\\nleq'); + // Greater than symbols + symbols.set('Not Greater Than', '\\ngtr'); + symbols.set('Greater Than or Equal', '\\geq'); + symbols.set('Not Greater Than or Equal', '\\ngeq'); + // Subset + symbols.set('Proper Subset', '\\subset'); + symbols.set('Not Proper Subset', '\\not\\subset'); + symbols.set('Subset', '\\subseteq'); + symbols.set('Not Subset', '\\nsubseteq'); + // Superset + symbols.set('Proper Superset', '\\supset'); + symbols.set('Not Proper Superset', '\\not\\supset'); + symbols.set('Superset', '\\supseteq'); + symbols.set('Not Superset', '\\nsupseteq'); + // Additional Set Notation + symbols.set('Member Of', '\\in'); + symbols.set('Not Member Of', '\\notin'); + symbols.set('Has Member', '\\ni'); + symbols.set('Union', '\\cup'); + symbols.set('Intersection', '\\cap'); + // Logic + symbols.set('There Exists', '\\ni'); + symbols.set('For All', '\\ni'); + symbols.set('Logical Not', '\\neg'); + symbols.set('Logical And', '\\land'); + symbols.set('Logical Or', '\\lor'); + + symbols.forEach((value: string, key: string) => { + const commandName = 'latex:' + key.replace(' ', '-').toLowerCase(); + app.commands.addCommand(commandName, { + label: key, + caption: value, + execute: async args => { + const widget = isLatexFile(editorTracker); + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + editor.replaceSelection(value); + } + } + } + }); + + symbolMenu.addItem({ + command: commandName, + args: {} + }); + }); + + app.commands.addCommand('latex:create-table', { + label: 'Create Table', + caption: 'Open a window to create a LaTeX table', + execute: async args => { + const rowResult = await InputDialog.getNumber({ + title: 'How many rows?' + }); + if (rowResult.button.accept) { + const colResult = await InputDialog.getNumber({ + title: 'How many columns?' + }); + if (colResult.button.accept) { + const widget = isLatexFile(editorTracker); + if (widget) { + const editor = widget.content.editor; + if (editor.replaceSelection) { + if (rowResult.value && colResult.value) { + editor.replaceSelection( + generateTable(rowResult.value, colResult.value) + ); + } + } + } + } + } + } + }); + + const menu = new Menu({ commands: app.commands }); + menu.title.label = 'LaTeX'; + menu.addItem({ + submenu: constantMenu, + type: 'submenu', + args: {} + }); + menu.addItem({ + submenu: symbolMenu, + type: 'submenu', + args: {} + }); + + menu.addItem({ + type: 'command', + command: 'latex:create-table' + }); + + mainMenu.addMenu(menu, true, { rank: 100 }); +} + +function generateTable(rowNum: number, colNum: number): string { + const columnConfig = 'c|'; + + let rowText = ''; + for (let i = 1; i <= rowNum * colNum; i++) { + if (i % colNum === 0) { + rowText += `cell${i} \\\\`; + if (i !== rowNum * colNum) { + rowText += '\n\\hline\n'; + } + } else { + rowText += `cell${i} & `; + } + } + + return `\\begin{center} + \\begin{tabular}{ |${columnConfig.repeat(colNum)} } + \\hline + ${rowText} + \\hline + \\end{tabular} + \\end{center}`.replace(/^ +/gm, ''); +} + /** * The list of file types for pdfs. */ diff --git a/style/icons/bold.svg b/style/icons/bold.svg new file mode 100644 index 0000000..5dc59b7 --- /dev/null +++ b/style/icons/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/center-align.svg b/style/icons/center-align.svg new file mode 100644 index 0000000..624ba7f --- /dev/null +++ b/style/icons/center-align.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/chart-column-solid.svg b/style/icons/chart-column-solid.svg new file mode 100644 index 0000000..6985b7b --- /dev/null +++ b/style/icons/chart-column-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/italic.svg b/style/icons/italic.svg new file mode 100644 index 0000000..f095c28 --- /dev/null +++ b/style/icons/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/left-align.svg b/style/icons/left-align.svg new file mode 100644 index 0000000..430fb92 --- /dev/null +++ b/style/icons/left-align.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/list.svg b/style/icons/list.svg new file mode 100644 index 0000000..3913160 --- /dev/null +++ b/style/icons/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/olist.svg b/style/icons/olist.svg new file mode 100644 index 0000000..55c1f2a --- /dev/null +++ b/style/icons/olist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/right-align.svg b/style/icons/right-align.svg new file mode 100644 index 0000000..9103d5e --- /dev/null +++ b/style/icons/right-align.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/table.svg b/style/icons/table.svg new file mode 100644 index 0000000..2ef9ca4 --- /dev/null +++ b/style/icons/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/icons/underline.svg b/style/icons/underline.svg new file mode 100644 index 0000000..9687cda --- /dev/null +++ b/style/icons/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 565297b..2a60efd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,11 +44,11 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0": - version: 7.25.4 - resolution: "@babel/runtime@npm:7.25.4" + version: 7.25.6 + resolution: "@babel/runtime@npm:7.25.6" dependencies: regenerator-runtime: ^0.14.0 - checksum: 5c2aab03788e77f1f959d7e6ce714c299adfc9b14fb6295c2a17eb7cad0dd9c2ebfb2d25265f507f68c43d5055c5cd6f71df02feb6502cea44b68432d78bcbbe + checksum: ee1a69d3ac7802803f5ee6a96e652b78b8addc28c6a38c725a4ad7d61a059d9e6cb9f6550ed2f63cce67a1bd82e0b1ef66a1079d895be6bfb536a5cfbd9ccc32 languageName: node linkType: hard @@ -70,14 +70,14 @@ __metadata: linkType: hard "@codemirror/commands@npm:^6.3.3": - version: 6.6.0 - resolution: "@codemirror/commands@npm:6.6.0" + version: 6.6.1 + resolution: "@codemirror/commands@npm:6.6.1" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.4.0 "@codemirror/view": ^6.27.0 "@lezer/common": ^1.1.0 - checksum: 53bb29f11f4453b7409836c41a9c13c0a8cb300e05ecc4928217330cf6e6735b1e5fb7fb831a2b1b8636593d6f3da42d016196ee1c8bb424f9cb73d55b8cb884 + checksum: 102cb14f1183eb33f6469148121912863264e281ba22b317915c8e883109d9522f0aa932c5315711a1ecf611fa7894552d6f8604688ca76c7af24d0db83df590 languageName: node linkType: hard @@ -92,15 +92,15 @@ __metadata: linkType: hard "@codemirror/lang-css@npm:^6.0.0, @codemirror/lang-css@npm:^6.2.1": - version: 6.2.1 - resolution: "@codemirror/lang-css@npm:6.2.1" + version: 6.3.0 + resolution: "@codemirror/lang-css@npm:6.3.0" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 "@lezer/common": ^1.0.2 - "@lezer/css": ^1.0.0 - checksum: 5a8457ee8a4310030a969f2d3128429f549c4dc9b7907ee8888b42119c80b65af99093801432efdf659b8ec36a147d2a947bc1ecbbf69a759395214e3f4834a8 + "@lezer/css": ^1.1.7 + checksum: e98e89fa436f0a27c95323efbb6a1c43a52ca0b9253ab3c12af16f38cb93670d42f8a63cc566e2f6b0348af2cdfa1a6c03cf045af2d6cb253b27b2efdce9b2b2 languageName: node linkType: hard @@ -558,6 +558,20 @@ __metadata: languageName: node linkType: hard +"@jupyterlab/attachments@npm:^4.2.5": + version: 4.2.5 + resolution: "@jupyterlab/attachments@npm:4.2.5" + dependencies: + "@jupyterlab/nbformat": ^4.2.5 + "@jupyterlab/observables": ^5.2.5 + "@jupyterlab/rendermime": ^4.2.5 + "@jupyterlab/rendermime-interfaces": ^3.10.5 + "@lumino/disposable": ^2.1.2 + "@lumino/signaling": ^2.1.2 + checksum: f49fc50f9889de9c7da88e004ae4dd562460da050ff373c946ec54863fcf293dacb5e15de57dbfb0b01141648989a873188a00b898cbb491bbd6c50140a0392c + languageName: node + linkType: hard + "@jupyterlab/builder@npm:^4.0.0": version: 4.2.5 resolution: "@jupyterlab/builder@npm:4.2.5" @@ -599,6 +613,42 @@ __metadata: languageName: node linkType: hard +"@jupyterlab/cells@npm:^4.2.5": + version: 4.2.5 + resolution: "@jupyterlab/cells@npm:4.2.5" + dependencies: + "@codemirror/state": ^6.4.1 + "@codemirror/view": ^6.26.0 + "@jupyter/ydoc": ^2.0.1 + "@jupyterlab/apputils": ^4.3.5 + "@jupyterlab/attachments": ^4.2.5 + "@jupyterlab/codeeditor": ^4.2.5 + "@jupyterlab/codemirror": ^4.2.5 + "@jupyterlab/coreutils": ^6.2.5 + "@jupyterlab/documentsearch": ^4.2.5 + "@jupyterlab/filebrowser": ^4.2.5 + "@jupyterlab/nbformat": ^4.2.5 + "@jupyterlab/observables": ^5.2.5 + "@jupyterlab/outputarea": ^4.2.5 + "@jupyterlab/rendermime": ^4.2.5 + "@jupyterlab/services": ^7.2.5 + "@jupyterlab/toc": ^6.2.5 + "@jupyterlab/translation": ^4.2.5 + "@jupyterlab/ui-components": ^4.2.5 + "@lumino/algorithm": ^2.0.1 + "@lumino/coreutils": ^2.1.2 + "@lumino/domutils": ^2.0.1 + "@lumino/dragdrop": ^2.1.4 + "@lumino/messaging": ^2.0.1 + "@lumino/polling": ^2.1.2 + "@lumino/signaling": ^2.1.2 + "@lumino/virtualdom": ^2.0.1 + "@lumino/widgets": ^2.3.2 + react: ^18.2.0 + checksum: 6b2f84c0036dbc8808eb6f5057d07dae00d8000fac2f91568ca3f9b6abe30e6724d1be7ce53f085f6e8a93850817316f4e9e2c0e4fb81c3b29e104908a570d3b + languageName: node + linkType: hard + "@jupyterlab/codeeditor@npm:^4.0.0, @jupyterlab/codeeditor@npm:^4.2.5": version: 4.2.5 resolution: "@jupyterlab/codeeditor@npm:4.2.5" @@ -749,7 +799,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/filebrowser@npm:^4.0.0": +"@jupyterlab/filebrowser@npm:^4.0.0, @jupyterlab/filebrowser@npm:^4.2.5": version: 4.2.5 resolution: "@jupyterlab/filebrowser@npm:4.2.5" dependencies: @@ -818,6 +868,7 @@ __metadata: "@jupyterlab/fileeditor": ^4.0.0 "@jupyterlab/launcher": ^4.0.0 "@jupyterlab/mainmenu": ^4.0.0 + "@jupyterlab/notebook": ^4.0.0 "@jupyterlab/services": ^7.0.0 "@jupyterlab/settingregistry": ^4.0.0 "@jupyterlab/statedb": ^4.0.0 @@ -927,6 +978,44 @@ __metadata: languageName: node linkType: hard +"@jupyterlab/notebook@npm:^4.0.0": + version: 4.2.5 + resolution: "@jupyterlab/notebook@npm:4.2.5" + dependencies: + "@jupyter/ydoc": ^2.0.1 + "@jupyterlab/apputils": ^4.3.5 + "@jupyterlab/cells": ^4.2.5 + "@jupyterlab/codeeditor": ^4.2.5 + "@jupyterlab/codemirror": ^4.2.5 + "@jupyterlab/coreutils": ^6.2.5 + "@jupyterlab/docregistry": ^4.2.5 + "@jupyterlab/documentsearch": ^4.2.5 + "@jupyterlab/lsp": ^4.2.5 + "@jupyterlab/nbformat": ^4.2.5 + "@jupyterlab/observables": ^5.2.5 + "@jupyterlab/rendermime": ^4.2.5 + "@jupyterlab/services": ^7.2.5 + "@jupyterlab/settingregistry": ^4.2.5 + "@jupyterlab/statusbar": ^4.2.5 + "@jupyterlab/toc": ^6.2.5 + "@jupyterlab/translation": ^4.2.5 + "@jupyterlab/ui-components": ^4.2.5 + "@lumino/algorithm": ^2.0.1 + "@lumino/coreutils": ^2.1.2 + "@lumino/disposable": ^2.1.2 + "@lumino/domutils": ^2.0.1 + "@lumino/dragdrop": ^2.1.4 + "@lumino/messaging": ^2.0.1 + "@lumino/polling": ^2.1.2 + "@lumino/properties": ^2.0.1 + "@lumino/signaling": ^2.1.2 + "@lumino/virtualdom": ^2.0.1 + "@lumino/widgets": ^2.3.2 + react: ^18.2.0 + checksum: 1c91b42e890407574451903af7d48db8c216fa9e27ecc4e60ee76366572029ff73be3974085427b72eaedf67e718a7d4f93207f7b66dd3cf27a0b51172ca7727 + languageName: node + linkType: hard + "@jupyterlab/observables@npm:^5.2.5": version: 5.2.5 resolution: "@jupyterlab/observables@npm:5.2.5" @@ -940,6 +1029,28 @@ __metadata: languageName: node linkType: hard +"@jupyterlab/outputarea@npm:^4.2.5": + version: 4.2.5 + resolution: "@jupyterlab/outputarea@npm:4.2.5" + dependencies: + "@jupyterlab/apputils": ^4.3.5 + "@jupyterlab/nbformat": ^4.2.5 + "@jupyterlab/observables": ^5.2.5 + "@jupyterlab/rendermime": ^4.2.5 + "@jupyterlab/rendermime-interfaces": ^3.10.5 + "@jupyterlab/services": ^7.2.5 + "@jupyterlab/translation": ^4.2.5 + "@lumino/algorithm": ^2.0.1 + "@lumino/coreutils": ^2.1.2 + "@lumino/disposable": ^2.1.2 + "@lumino/messaging": ^2.0.1 + "@lumino/properties": ^2.0.1 + "@lumino/signaling": ^2.1.2 + "@lumino/widgets": ^2.3.2 + checksum: 0e2834244dfc12491d7207e9749c92caaa44424e5541cb227f5933a61884e6d42c67791f5c8982cbefebf6b7ce94fe595e633571d9ebc381dd130616899a4291 + languageName: node + linkType: hard + "@jupyterlab/rendermime-interfaces@npm:^3.10.5": version: 3.10.5 resolution: "@jupyterlab/rendermime-interfaces@npm:3.10.5" @@ -1121,14 +1232,14 @@ __metadata: languageName: node linkType: hard -"@lezer/css@npm:^1.0.0, @lezer/css@npm:^1.1.0": - version: 1.1.8 - resolution: "@lezer/css@npm:1.1.8" +"@lezer/css@npm:^1.1.0, @lezer/css@npm:^1.1.7": + version: 1.1.9 + resolution: "@lezer/css@npm:1.1.9" dependencies: "@lezer/common": ^1.2.0 "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: 1f5968360dbac7ba27f0c2a194143769f7b01824715274dd8507dacf13cc790bb8c48ce95de355e9c58be93bb3e271bf98b9fc51213f79e4ce918e7c7ebbef04 + checksum: 25c63475061a3c9f87961a7f85c5f547f14fb7e81b0864675d2206999a874a0559d676145c74c6ccde39519dbc8aa33e216265f5366d08060507b6c9e875fe0f languageName: node linkType: hard @@ -1207,12 +1318,12 @@ __metadata: linkType: hard "@lezer/markdown@npm:^1.0.0, @lezer/markdown@npm:^1.2.0": - version: 1.3.0 - resolution: "@lezer/markdown@npm:1.3.0" + version: 1.3.1 + resolution: "@lezer/markdown@npm:1.3.1" dependencies: "@lezer/common": ^1.0.0 "@lezer/highlight": ^1.0.0 - checksum: 13eb2720e4cb84278349bad8af116f748813094f99fad02680010c3a8c5985e0358c344487990f87a31ef0d6c1a2be582301f914c0e4a6e9cfa22647b6cd6545 + checksum: b5cbb857a90411e174e7ad23433756a81cf2ab422ef749e529211e078ed4061b4595fa8cbcca56119919c0b2735e8ecac11ff34768d64cb90e599fde2bc6c730 languageName: node linkType: hard @@ -1495,8 +1606,8 @@ __metadata: linkType: hard "@rjsf/core@npm:^5.13.4": - version: 5.20.1 - resolution: "@rjsf/core@npm:5.20.1" + version: 5.21.0 + resolution: "@rjsf/core@npm:5.21.0" dependencies: lodash: ^4.17.21 lodash-es: ^4.17.21 @@ -1506,14 +1617,15 @@ __metadata: peerDependencies: "@rjsf/utils": ^5.20.x react: ^16.14.0 || >=17 - checksum: a75a5261090bc1dd46594060981a130721060c38805031d1554b077e46673f84ffb40c489c2b579cf50e4fbb709210585a139e1c5d9eaccd603d957e95c2ead2 + checksum: 6b4a0a026461c49f19a1fb098ad7daa33f6bc97d089fbf571ea820b31e6b1852e58cd1f5e02a4d46bc7e07f018c9d2cfa4eb35ebc39cdde5345706254a0408b3 languageName: node linkType: hard "@rjsf/utils@npm:^5.13.4": - version: 5.20.1 - resolution: "@rjsf/utils@npm:5.20.1" + version: 5.21.0 + resolution: "@rjsf/utils@npm:5.21.0" dependencies: + fast-equals: ^5.0.1 json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 lodash: ^4.17.21 @@ -1521,7 +1633,7 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: 8bf59caeb9d32d40ec492df7fc702f0573ad2054042a3a0676fb7e10afa56d0d85740be28a752782fc16273952553ab76b40eb17e4e5b1178f473628003109f4 + checksum: b1f87c1e26544ed5531d387a85644661cb41095b86ec73ed0d03e21f6743cf3ed7324315112e43e6c973e7a81e2313534ba201295d08cbd1ec314d79752f92b5 languageName: node linkType: hard @@ -1561,11 +1673,11 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 22.5.1 - resolution: "@types/node@npm:22.5.1" + version: 22.5.4 + resolution: "@types/node@npm:22.5.4" dependencies: undici-types: ~6.19.2 - checksum: 366990347c12e08e9ffe113e493253ac454d5337828e23cb02c790d5abe1f2ab0148da37b728eb886d858c4c5f29f4341a29987e89555d482ea236691d9d2648 + checksum: 77ac225c38c428200036780036da0bc6764e2721cfa8f528c7e7da7cfefe01a32a5791e28a54efbeedbc977949058d7db902b2e00139298225d4686cee4ae6db languageName: node linkType: hard @@ -1593,12 +1705,12 @@ __metadata: linkType: hard "@types/react@npm:*, @types/react@npm:^18.0.26, @types/react@npm:^18.2.6": - version: 18.3.4 - resolution: "@types/react@npm:18.3.4" + version: 18.3.5 + resolution: "@types/react@npm:18.3.5" dependencies: "@types/prop-types": "*" csstype: ^3.0.2 - checksum: 555ccd1af86a23c781dea0360de64b2f7a0708cdcbf9e6496744b77630065868526fd55147c727dc5ef11b7fd712b04f7898757a84c67e2eb9dfd4c4ead10d95 + checksum: 63d2ff473b348c902b68c20be55d2c5124d078c4336c2d1778f316c27789ed596657e8e714022ce14fb24994b0960fc64c913e629bb0bf85815355b0c31eb46b languageName: node linkType: hard @@ -2440,9 +2552,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001646": - version: 1.0.30001653 - resolution: "caniuse-lite@npm:1.0.30001653" - checksum: 289cf06c26a46f3e6460ccd5feffa788ab0ab35d306898c48120c65cfb11959bfa560e9f739393769b4fd01150c69b0747ad3ad5ec3abf3dfafd66df3c59254e + version: 1.0.30001660 + resolution: "caniuse-lite@npm:1.0.30001660" + checksum: 8b2c5de2f5facd31980426afbba68238270984acfe8c1ae925b8b6480448eea2fae292f815674617e9170c730c8a238d7cc0db919f184dc0e3cd9bec18f5e5ad languageName: node linkType: hard @@ -2858,14 +2970,14 @@ __metadata: linkType: hard "debug@npm:^4.0.1, debug@npm:^4.1.1, debug@npm:^4.3.4": - version: 4.3.6 - resolution: "debug@npm:4.3.6" + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: - ms: 2.1.2 + ms: ^2.1.3 peerDependenciesMeta: supports-color: optional: true - checksum: 1630b748dea3c581295e02137a9f5cbe2c1d85fea35c1e6597a65ca2b16a6fce68cec61b299d480787ef310ba927dc8c92d3061faba0ad06c6a724672f66be7f + checksum: 822d74e209cd910ef0802d261b150314bbcf36c582ccdbb3e70f0894823c17e49a50d3e66d96b633524263975ca16b6a833f3e3b7e030c157169a5fabac63160 languageName: node linkType: hard @@ -3061,9 +3173,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.4": - version: 1.5.13 - resolution: "electron-to-chromium@npm:1.5.13" - checksum: f18ac84dd3bf9a200654a6a9292b9ec4bced0cf9bd26cec9941b775f4470c581c9d043e70b37a124d9752dcc0f47fc96613d52b2defd8e59632852730cb418b9 + version: 1.5.19 + resolution: "electron-to-chromium@npm:1.5.19" + checksum: 459b47ab828cbeb2d09767c93cd181bdccbda008e1d7fc92d078d72ecf4cac3107d2deb424016a63aadc484765ec98c84f339546b0a627892d24eb286f9f0adb languageName: node linkType: hard @@ -3251,9 +3363,9 @@ __metadata: linkType: hard "escalade@npm:^3.1.2": - version: 3.1.2 - resolution: "escalade@npm:3.1.2" - checksum: 1ec0977aa2772075493002bdbd549d595ff6e9393b1cb0d7d6fcaf78c750da0c158f180938365486f75cb69fba20294351caddfce1b46552a7b6c3cde52eaa02 + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 47b029c83de01b0d17ad99ed766347b974b0d628e848de404018f3abee728e987da0d2d370ad4574aa3d5b5bfc368754fd085d69a30f8e75903486ec4b5b709e languageName: node linkType: hard @@ -3554,6 +3666,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^5.0.1": + version: 5.0.1 + resolution: "fast-equals@npm:5.0.1" + checksum: fbb3b6a74f3a0fa930afac151ff7d01639159b4fddd2678b5d50708e0ba38e9ec14602222d10dadb8398187342692c04fbef5a62b1cfcc7942fe03e754e064bc + languageName: node + linkType: hard + "fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" @@ -5504,14 +5623,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:^2.1.1": +"ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -5968,9 +6080,9 @@ __metadata: linkType: hard "picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": - version: 1.0.1 - resolution: "picocolors@npm:1.0.1" - checksum: fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 + version: 1.1.0 + resolution: "picocolors@npm:1.1.0" + checksum: a64d653d3a188119ff45781dfcdaeedd7625583f45280aea33fcb032c7a0d3959f2368f9b192ad5e8aade75b74dbd954ffe3106c158509a45e4c18ab379a2acd languageName: node linkType: hard @@ -6130,13 +6242,13 @@ __metadata: linkType: hard "postcss@npm:^8.3.11, postcss@npm:^8.4.28, postcss@npm:^8.4.33": - version: 8.4.41 - resolution: "postcss@npm:8.4.41" + version: 8.4.45 + resolution: "postcss@npm:8.4.45" dependencies: nanoid: ^3.3.7 picocolors: ^1.0.1 source-map-js: ^1.2.0 - checksum: f865894929eb0f7fc2263811cc853c13b1c75103028b3f4f26df777e27b201f1abe21cb4aa4c2e901c80a04f6fb325ee22979688fe55a70e2ea82b0a517d3b6f + checksum: 3223cdad4a9392c0b334ee3ee7e4e8041c631cb6160609cef83c18d2b2580e931dd8068ab13cc6000c1a254d57492ac6c38717efc397c5dcc9756d06bc9c44f3 languageName: node linkType: hard @@ -6198,12 +6310,12 @@ __metadata: linkType: hard "pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" + version: 3.0.2 + resolution: "pump@npm:3.0.2" dependencies: end-of-stream: ^1.1.0 once: ^1.3.1 - checksum: e42e9229fba14732593a718b04cb5e1cfef8254544870997e0ecd9732b189a48e1256e4e5478148ecb47c8511dca2b09eae56b4d0aad8009e6fac8072923cfc9 + checksum: e0c4216874b96bd25ddf31a0b61a5613e26cc7afa32379217cf39d3915b0509def3565f5f6968fafdad2894c8bbdbd67d340e84f3634b2a29b950cffb6442d9f languageName: node linkType: hard @@ -6900,9 +7012,9 @@ __metadata: linkType: hard "source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.0": - version: 1.2.0 - resolution: "source-map-js@npm:1.2.0" - checksum: 791a43306d9223792e84293b00458bf102a8946e7188f3db0e4e22d8d530b5f80a4ce468eb5ec0bf585443ad55ebbd630bf379c98db0b1f317fd902500217f97 + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b languageName: node linkType: hard @@ -7453,8 +7565,8 @@ __metadata: linkType: hard "terser@npm:^5.26.0": - version: 5.31.6 - resolution: "terser@npm:5.31.6" + version: 5.32.0 + resolution: "terser@npm:5.32.0" dependencies: "@jridgewell/source-map": ^0.3.3 acorn: ^8.8.2 @@ -7462,7 +7574,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: 60d3faf39c9ad7acc891e17888bbd206e0b777f442649cf49873a5fa317b8b8a17179a46970d884d5f93e8addde0206193ed1e2e4f1ccb1cafb167f7d1ddee96 + checksum: 61e7c064ed693222c67413294181713798258ab4326b2f0b14ce889df530fb9683e7f2af2dfd228f1b9aae90c38c44dcbdfd22c0a3e020c56dff2770d1dc4a6d languageName: node linkType: hard @@ -8209,11 +8321,11 @@ __metadata: linkType: hard "yjs@npm:^13.5.40, yjs@npm:^13.6.1": - version: 13.6.18 - resolution: "yjs@npm:13.6.18" + version: 13.6.19 + resolution: "yjs@npm:13.6.19" dependencies: lib0: ^0.2.86 - checksum: 5c9f8f31f5f9f30f17680a765b015e4274820fe10fb6bf6a7d39dee2ff0493a81ace02d11bff6f18c6799cade2bcfc9fc2d7b6ca8bc1eb167c4ac2f3789c0f01 + checksum: 1b6e1454128aa85d720dec41deab1a88f903e8a486532813dba1895752a3fbef468df0bd5549928d77eab232a25113501f794f7347e4e60e3b5efe8382f513a4 languageName: node linkType: hard