From 6bfa2fd696a306a984b29c6ea11fa66e93e0474c Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 4 Jun 2024 12:48:41 -0400
Subject: changes

---
 src/client/views/MainView.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 31d88fb87..f61c784df 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -548,6 +548,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faRobot,
                 fa.faSatellite,
                 fa.faStar,
+                fa.faCloud
             ]
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 3f940bd45fd1bf72f6c16edc188f754b01f689ba Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 21 Jun 2024 15:11:23 -0400
Subject: yay dragging

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/views/MainView.tsx                      |   3 +-
 .../views/collections/CollectionCardDeckView.tsx   | 231 ++++++++++++---------
 4 files changed, 149 insertions(+), 100 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 34407c641..a190fe11b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -479,8 +479,9 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
-    cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
+    // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
     cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
+    cardSort_activeIcons?: List<string>;
     cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
 }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2ca2ce9c9..94eee6711 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -660,13 +660,15 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
-                title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
+            // { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
+            //     title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
             // { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
             //   btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            // { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "AI Sort",     icon:"palette",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
             { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
             // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f61c784df..b06d785e1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -548,7 +548,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faRobot,
                 fa.faSatellite,
                 fa.faStar,
-                fa.faCloud
+                fa.faCloud,
+                fa.faBolt
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 76536ed48..2ad7f0ed1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
-import { Doc, NumListCast } from '../../../fields/Doc';
+import { Doc, NumListCast, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -27,12 +27,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Tooltip } from '@mui/material';
 import { dropActionType } from '../../util/DropActionTypes';
+import { List } from '../../../fields/List';
 
 enum cardSortings {
     Time = 'time',
     Type = 'type',
     Color = 'color',
     Custom = 'custom',
+    Drag = 'drag',
     None = '',
 }
 @observer
@@ -48,7 +50,7 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
-    @observable _docsDraggedIndex: number[] = [];
+    @observable _docDraggedIndex: number = -1;
     @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
@@ -73,12 +75,6 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
-        this._dropDisposer?.();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
-        }
-    };
 
     constructor(props: any) {
         super(props);
@@ -145,12 +141,7 @@ export class CollectionCardView extends CollectionSubView() {
         return regularDocs;
     }
 
-    /**
-     * Determines the order in which the cards will be rendered depending on the current sort type
-     */
-    @computed get sortedDocs() {
-        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc));
-    }
+   
 
     /**
      * Number of rows of cards to be rendered
@@ -226,27 +217,71 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
     };
 
-    /**
-     * Translates the selected node to the middle fo the screen
-     * @param index
-     * @returns
-     */
-    // translateSelected = (index: number): number => {
-    //     // if (this.isSelected(index)) {
-    //     const middleOfPanel = this._props.PanelWidth() / 2;
-    //     const scaledNodeWidth = this.panelWidth() * 1.25;
+     findCardDropIndex = (mouseX: number, mouseY: number) => {
+
+        const amCardsTotal = this.sortedDocs.length
+        let index = 0;
+        const cardWidth = amCardsTotal < this._maxRowCount ? this._props.PanelWidth() / amCardsTotal : this._props.PanelWidth() /  this._maxRowCount;
+    
+        // Calculate the adjusted X position accounting for the initial offset
+        let adjustedX = mouseX
+
+        const amRows = Math.ceil(amCardsTotal / this._maxRowCount);
+        const rowHeight = this._props.PanelHeight( ) / amRows
+        const currRow = Math.floor((mouseY - 100) / rowHeight) //rows start at 0
+
+        if (adjustedX < 0) {
+            return 0; // Before the first column
+        }
+
+        if (amCardsTotal < this._maxRowCount)   { 
+            index = Math.floor(adjustedX / cardWidth);
+        }
+
+        else if (currRow != amRows -1 ){
+            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount)
+        }
 
-    //     // Calculate the position of the node's left edge before scaling
-    //     const nodeLeftEdge = index * this.panelWidth();
-    //     // Find the center of the node after scaling
-    //     const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
+        else {
+            // console.log(amRows + "am rows")
+            const rowAmCards = amCardsTotal - (currRow * this._maxRowCount)
+            const offset = ((this._maxRowCount - rowAmCards ) / 2) * cardWidth
+            adjustedX = mouseX - offset
 
-    //     // Calculate the translation needed to align the scaled node's center with the panel's center
-    //     const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
+            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount);
+        }
+        return index;
+    };
 
-    //     return translation;
-    // };
+    @action
+    onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
+        if (DragManager.docsBeingDragged.length != 0 ) {
+            this._isACardBeingDragged = true
+
+            const newIndex = this.findCardDropIndex(e.clientX, e.clientY);
+            
+            if (newIndex !== this._docDraggedIndex && newIndex != -1) {
+                this._docDraggedIndex = newIndex;
+                }
+        }
+    };
 
+
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            this._isACardBeingDragged = false;
+            this._docDraggedIndex = -1;
+            e.stopPropagation()
+            const draggedDocs = de.complete.docDragData?.draggedDocuments;
+            return true;
+        }
+        return false;
+    };
+
+     @computed get sortedDocs() {
+        // console.log("hi hi hi")
+        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc), this._docDraggedIndex);
+    }
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
      * front, latter cards to the back
@@ -255,7 +290,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean) => {
+    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+        // console.log('HIIIIIIIII')
         if (sortType === cardSortings.None) return docs;
         docs.sort((docA, docB) => {
             const [typeA, typeB] = (() => {
@@ -281,9 +317,25 @@ export class CollectionCardView extends CollectionSubView() {
             return isDesc ? -out : out; // Reverse the sort order if descending is true
         });
 
+        if (dragIndex != -1){
+            // console.log("what what")
+
+            const draggedDoc = DragManager.docsBeingDragged[0]
+            const originalIndex = docs.findIndex(doc => doc === draggedDoc);
+
+                    
+            // Remove the dragged document from its original position
+            docs.splice(originalIndex, 1);
+            // Insert the dragged document at the new position
+            docs.splice(dragIndex, 0, draggedDoc);
+                // }
+        }
+
         return docs;
     };
 
+
+
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
             // eslint-disable-next-line react/jsx-props-no-spreading
@@ -361,16 +413,7 @@ export class CollectionCardView extends CollectionSubView() {
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
 
-    /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
-     * @param childPairIndex
-     * @param buttonID
-     * @param doc
-     */
-    toggleButton = undoable((buttonID: number, doc: Doc) => {
-        this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
-    }, 'toggle custom button');
-
+  
     /**
      * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words.
      * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is
@@ -456,27 +499,37 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
-        if (cardSort !== cardSortings.Custom) return null;
+        // if (cardSort !== cardSortings.Custom) return null;
 
-        const amButtons = Math.max(
-            4,
-            this.childDocs?.reduce((set, d) => {
-                if (this.cardSort_customField) {
-                    set.add(NumCast(d[this.cardSort_customField]));
-                }
-                return set;
-            }, new Set<number>()).size ?? 0
-        );
+        const amButtons = 4
+
+        // const amButtons = Math.max(
+        //     4,
+        //     this.childDocs?.reduce((set, d) => {
+        //         if (this.cardSort_customField) {
+        //             set.add(NumCast(d[this.cardSort_customField]));
+        //         }
+        //         return set;
+        //     }, new Set<number>()).size ?? 0
+        // );
 
         const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+
         const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
 
+        const iconMap: { [key: number]: any } = {
+            0: 'star',
+            1: 'heart',
+            2: 'cloud',
+            3: 'bolt'
+        };
+
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
                 {numberRange(amButtons).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from group {i + 1}</div>}>
-                        <button type="button" onClick={() => this.toggleButton(i, doc)}>
-                            {this.getButtonIcon(activeButtonIndex === i)}
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}>
+                        <button type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}>
+                            {this.getButtonIcon(doc, iconMap[i])}
                         </button>
                     </Tooltip>
                 ))}
@@ -484,53 +537,44 @@ export class CollectionCardView extends CollectionSubView() {
         );
     };
 
-    getButtonIcon = (isActive: boolean): JSX.Element => {
-        const iconMap: { [key: string]: any } = {
-            like: 'heart',
-            chat: 'robot',
-            idea: 'cloud',
-        };
+      /**
+     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
+     * @param childPairIndex
+     * @param buttonID
+     * @param doc
+     */
+      toggleButton = undoable((doc: Doc, icon: string) => {
+
+
+
+        // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
+
+        // doc.cardSort_activeIcons = new List<string>()
+
 
-        const icon = iconMap[this.cardSort_customField ?? ''] || 'star';
+        const list = StrListCast(doc.cardSort_activeIcons);
+        doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+
+
+
+        // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID])
+    }, 'toggle card tag');
+
+
+    getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
+
+        const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
+
+        console.log(StrListCast(doc.cardSort_activeIcons))
         const color = isActive ? '#4476f7' : '#323232';
 
         return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
     };
 
-    // @action
-    // onDragStart = (index: number) => {
-    //     this.draggedIndex = index;
-    // };
-
-    // @action
-    // onDragOver = (index: number) => {
-    //     if (this.draggedIndex !== index) {
-    //         this.overIndex = index;
-    //     }
-    // };
-
-    // @action
-    // onDrop = () => {
-    //     if (this.draggedIndex !== -1 && this.overIndex !== -1) {
-    //         const draggedDoc = this.sortedDocs[this.draggedIndex];
-    //         this.sortedDocs.splice(this.draggedIndex, 1);
-    //         this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
-    //         this.draggedIndex = -1;
-    //         this.overIndex = -1;
-    //     }
-    // };
-
-    // @action
-    // onDragEnd = () => {
-    //     this.draggedIndex = -1;
-    //     this.overIndex = -1;
-    // };
-
     /**
      * Actually renders all the cards
      */
     renderCards = () => {
-        console.log(DragManager.docsBeingDragged.length + 'drag ha');
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
 
@@ -599,8 +643,9 @@ export class CollectionCardView extends CollectionSubView() {
     render() {
         return (
             <div
+                onPointerMove={e => this.onPointerMove(e)}
                 className="collectionCardView-outer"
-                ref={this.createDashEventsTarget}
+                ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
-- 
cgit v1.2.3-70-g09d2


From 3e46e59b3919458d945745f457d8c76f13094796 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sun, 14 Jul 2024 14:06:10 -0400
Subject: in it to win it

---
 package-lock.json                                  | 2150 ++++++++++++++------
 src/client/util/CurrentUserUtils.ts                |    3 +-
 src/client/views/MainView.tsx                      |    3 +-
 .../views/collections/CollectionCardDeckView.tsx   |   15 +-
 src/client/views/global/globalScripts.ts           |   14 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   19 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   98 +-
 7 files changed, 1677 insertions(+), 625 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 61eccb86e..0542a29cf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3,382 +3,70 @@
     "version": "1.0.0",
     "lockfileVersion": 1,
     "requires": true,
-    "packages": {
-        "": {
-            "name": "dash",
-            "version": "1.0.0",
-            "dependencies": {
-                "@adobe/react-spectrum": "^3.32.2",
-                "@azure/storage-blob": "^12.17.0",
-                "@babel/preset-env": "^7.23.5",
-                "@babel/preset-react": "^7.23.3",
-                "@bundled-es-modules/pdfjs-dist": "^3.6.172-alpha.1",
-                "@emotion/react": "^11.11.1",
-                "@emotion/styled": "^11.11.0",
-                "@ffmpeg/core": "^0.12.5",
-                "@ffmpeg/ffmpeg": "^0.12.8",
-                "@fortawesome/fontawesome-svg-core": "^6.5.1",
-                "@fortawesome/free-brands-svg-icons": "^6.5.1",
-                "@fortawesome/free-regular-svg-icons": "^6.5.1",
-                "@fortawesome/free-solid-svg-icons": "^6.5.1",
-                "@fortawesome/react-fontawesome": "^0.2.0",
-                "@fullcalendar/core": "^6.1.10",
-                "@fullcalendar/daygrid": "^6.1.10",
-                "@fullcalendar/multimonth": "^6.1.10",
-                "@internationalized/date": "^3.5.0",
-                "@mui/icons-material": "^5.14.19",
-                "@mui/material": "^5.14.19",
-                "@octokit/core": "^6.0.1",
-                "@react-google-maps/api": "^2.19.2",
-                "@react-spring/web": "^9.7.3",
-                "@turf/turf": "^6.5.0",
-                "@types/bezier-js": "^4.1.3",
-                "@types/brotli": "^1.3.4",
-                "@types/cors": "^2.8.17",
-                "@types/d3-axis": "^3.0.6",
-                "@types/d3-color": "^3.1.3",
-                "@types/d3-scale": "^4.0.8",
-                "@types/d3-selection": "^3.0.10",
-                "@types/dom-speech-recognition": "0.0.4",
-                "@types/find-in-files": "^0.5.3",
-                "@types/fluent-ffmpeg": "^2.1.24",
-                "@types/formidable": "3.4.5",
-                "@types/geojson": "^7946.0.14",
-                "@types/google-maps": "^3.2.6",
-                "@types/mapbox-gl": "^3.1.0",
-                "@types/pdf-parse": "^1.1.4",
-                "@types/reveal": "^4.2.0",
-                "@types/supercluster": "^7.1.3",
-                "@types/web": "^0.0.147",
-                "@types/webpack-hot-middleware": "^2.25.9",
-                "@typescript-eslint/parser": "^7.8.0",
-                "@webscopeio/react-textarea-autocomplete": "^4.9.2",
-                "adm-zip": "^0.5.10",
-                "archiver": "^7.0.1",
-                "async": "^3.2.5",
-                "axios": "^1.6.2",
-                "babel": "^6.23.0",
-                "babel-loader": "^9.1.3",
-                "bcrypt-nodejs": "0.0.3",
-                "better-react-mathjax": "^2.0.3",
-                "bezier-curve": "^1.0.0",
-                "bezier-js": "^6.1.4",
-                "bingmaps-react": "^1.2.10",
-                "bluebird": "^3.7.2",
-                "body-parser": "^1.20.2",
-                "bootstrap": "^5.3.2",
-                "brotli": "^1.3.3",
-                "browndash-components": "^0.1.44",
-                "browser-assert": "^1.2.1",
-                "bson": "^6.2.0",
-                "canvas": "^2.11.2",
-                "chart.js": "^4.4.0",
-                "child_process": "^1.0.2",
-                "class-transformer": "^0.5.1",
-                "color": "^4.2.3",
-                "colors": "^1.4.0",
-                "compute-cosine-similarity": "^1.1.0",
-                "connect-flash": "^0.1.1",
-                "connect-mongo": "^5.1.0",
-                "cookie-parser": "^1.4.6",
-                "cookie-session": "^2.0.0",
-                "core-js": "^3.33.3",
-                "cors": "^2.8.5",
-                "css-loader": "^7.0.0",
-                "csstype": "^3.1.3",
-                "csv-parser": "^3.0.0",
-                "csv-stringify": "^6.4.4",
-                "csvtojson": "^2.0.10",
-                "D": "^1.0.0",
-                "d3": "^7.8.5",
-                "depcheck": "^1.4.7",
-                "dotenv": "^16.3.1",
-                "eslint-webpack-plugin": "^4.1.0",
-                "exif": "^0.6.0",
-                "exifr": "^7.1.3",
-                "express": "^4.18.2",
-                "express-flash": "0.0.2",
-                "express-session": "^1.17.3",
-                "express-validator": "^7.0.1",
-                "extract-colors": "^4.0.2",
-                "ffmpeg": "0.0.4",
-                "file-loader": "^6.2.0",
-                "file-saver": "^2.0.5",
-                "find-in-files": "^0.5.0",
-                "fit-curve": "^0.2.0",
-                "flexlayout-react": "^0.7.15",
-                "fluent-ffmpeg": "^2.1.2",
-                "forever-agent": "^0.6.1",
-                "fork-ts-checker-webpack-plugin": "^9.0.2",
-                "form-data": "^4.0.0",
-                "formidable": "3.5.1",
-                "function-plot": "^1.23.3",
-                "golden-layout": "^2.6.0",
-                "google-auth-library": "^9.4.1",
-                "googleapis": "^133.0.0",
-                "googlephotos": "^0.3.5",
-                "got": "^14.0.0",
-                "howler": "^2.2.4",
-                "html-to-image": "^1.11.11",
-                "html-to-text": "^9.0.5",
-                "html-webpack-plugin": "^5.5.3",
-                "http-browserify": "^1.7.0",
-                "https": "^1.0.0",
-                "https-browserify": "^1.0.0",
-                "i": "^0.3.7",
-                "image-data-uri": "^2.0.1",
-                "image-size": "^1.0.2",
-                "image-size-stream": "^1.1.0",
-                "is-plain-obj": "^4.1.0",
-                "jimp": "^0.22.10",
-                "jpeg-autorotate": "^9.0.0",
-                "jquery": "^3.7.1",
-                "js-datepicker": "^5.18.2",
-                "jsonschema": "^1.4.1",
-                "jszip": "^3.10.1",
-                "lodash": "^4.17.21",
-                "mapbox-gl": "^3.0.1",
-                "markdown-it": "^14.1.0",
-                "mathquill": "^0.10.1-a",
-                "md5-file": "^5.0.0",
-                "memorystream": "^0.3.1",
-                "mermaid": "^10.9.0",
-                "mobile-detect": "^1.4.5",
-                "mobx": "^6.12.0",
-                "mobx-react": "^9.1.0",
-                "mobx-utils": "^6.0.8",
-                "mongodb": "^6.3.0",
-                "mongoose": "^8.0.2",
-                "node-stream-zip": "^1.15.0",
-                "nodemailer": "^6.9.7",
-                "nodemon": "^3.0.2",
-                "npm": "^10.2.5",
-                "openai": "^4.26.0",
-                "p-limit": "^5.0.0",
-                "passport": "^0.7.0",
-                "passport-google-oauth20": "^2.0.0",
-                "passport-local": "^1.0.0",
-                "path-browserify": "^1.0.1",
-                "pdf-parse": "^1.1.1",
-                "pdfjs": "^2.4.7",
-                "pdfjs-dist": "^4.0.269",
-                "probe-image-size": "^7.2.3",
-                "process": "^0.11.10",
-                "prosemirror-commands": "^1.5.2",
-                "prosemirror-find-replace": "^0.9.0",
-                "prosemirror-history": "^1.3.2",
-                "prosemirror-inputrules": "^1.4.0",
-                "prosemirror-keymap": "^1.2.2",
-                "prosemirror-model": "^1.19.3",
-                "prosemirror-schema-list": "^1.3.0",
-                "prosemirror-state": "^1.4.3",
-                "prosemirror-transform": "^1.8.0",
-                "prosemirror-view": "^1.32.5",
-                "pug": "^3.0.2",
-                "query-string": "^7.1.3",
-                "querystring-es3": "^0.2.1",
-                "raw-loader": "^4.0.2",
-                "rc-switch": "^4.1.0",
-                "react": "^18.2.0",
-                "react-autosuggest": "^10.1.0",
-                "react-awesome-reveal": "^4.2.7",
-                "react-color": "^2.19.3",
-                "react-compound-slider": "^3.4.0",
-                "react-datepicker": "^6.1.0",
-                "react-dom": "^18.2.0",
-                "react-grid-layout": "^1.4.4",
-                "react-icons": "^5.0.1",
-                "react-jsx-parser": "^1.29.0",
-                "react-latex-next": "^3.0.0",
-                "react-loading": "^2.0.3",
-                "react-map-gl": "^7.1.6",
-                "react-markdown": "^9.0.1",
-                "react-measure": "^2.5.2",
-                "react-resizable": "^3.0.5",
-                "react-select": "^5.8.0",
-                "react-textarea-autosize": "^8.5.3",
-                "react-type-animation": "^3.2.0",
-                "react-xarrows": "^2.0.2",
-                "readline": "^1.3.0",
-                "recharts": "^2.10.3",
-                "rehype-katex": "^7.0.0",
-                "rehype-raw": "^7.0.0",
-                "remark-gfm": "^4.0.0",
-                "remark-math": "^6.0.0",
-                "request": "^2.88.2",
-                "request-promise": "^4.2.6",
-                "reveal.js": "^5.0.2",
-                "rimraf": "^5.0.5",
-                "sass": "^1.69.5",
-                "sass-loader": "^14.2.0",
-                "serializr": "^3.0.2",
-                "shelljs": "^0.8.5",
-                "socket.io": "^4.7.2",
-                "socket.io-client": "^4.7.2",
-                "standard-http-error": "^2.0.1",
-                "stream-browserify": "^3.0.0",
-                "styled-components": "^6.1.1",
-                "supercluster": "^8.0.1",
-                "textarea-caret": "^3.1.0",
-                "tough-cookie": "^4.1.3",
-                "tslint": "^6.1.3",
-                "tslint-loader": "^3.5.4",
-                "typescript": "^5.3.3",
-                "typescript-collections": "^1.3.3",
-                "typescript-language-server": "^4.1.3",
-                "uninstall": "^0.0.0",
-                "url": "^0.11.3",
-                "url-loader": "^4.1.1",
-                "util": "^0.12.5",
-                "uuid": "^9.0.1",
-                "valid-url": "^1.0.9",
-                "web-request": "^1.0.7",
-                "webpack": "^5.89.0",
-                "webpack-cli": "^5.1.4",
-                "webpack-dev-middleware": "^7.0.0",
-                "webpack-hot-middleware": "^2.25.4",
-                "wikijs": "^6.4.1",
-                "words-to-numbers": "^1.5.1",
-                "xoauth2": "^1.2.0",
-                "xregexp": "^5.1.1"
-            },
-            "devDependencies": {
-                "@eslint/js": "^9.1.1",
-                "@types/adm-zip": "^0.5.5",
-                "@types/animejs": "^3.1.12",
-                "@types/archiver": "^6.0.2",
-                "@types/async": "^3.2.24",
-                "@types/bcrypt-nodejs": "0.0.31",
-                "@types/bluebird": "^3.5.42",
-                "@types/body-parser": "^1.19.5",
-                "@types/chai": "^4.3.11",
-                "@types/color": "^3.0.6",
-                "@types/cookie-parser": "^1.4.6",
-                "@types/cookie-session": "^2.0.48",
-                "@types/d3": "^7.4.3",
-                "@types/exif": "^0.6.5",
-                "@types/express": "^4.17.21",
-                "@types/express-session": "^1.17.10",
-                "@types/file-saver": "^2.0.7",
-                "@types/howler": "^2.2.11",
-                "@types/html-to-text": "^9.0.4",
-                "@types/jquery": "^3.5.29",
-                "@types/libxmljs": "^0.18.12",
-                "@types/lodash": "^4.14.202",
-                "@types/mocha": "^10.0.6",
-                "@types/node": "^20.10.3",
-                "@types/nodemailer": "^6.4.14",
-                "@types/passport": "^1.0.16",
-                "@types/passport-google-oauth20": "^2.0.14",
-                "@types/passport-local": "^1.0.38",
-                "@types/rc-switch": "^1.9.5",
-                "@types/react": "^18.2.41",
-                "@types/react-autosuggest": "^10.1.10",
-                "@types/react-color": "^3.0.10",
-                "@types/react-datepicker": "^6.2.0",
-                "@types/react-dom": "^18.2.17",
-                "@types/react-grid-layout": "^1.3.5",
-                "@types/react-measure": "^2.0.12",
-                "@types/react-reconciler": "^0.28.8",
-                "@types/request": "^2.48.12",
-                "@types/request-promise": "^4.1.51",
-                "@types/shelljs": "^0.8.15",
-                "@types/uuid": "^9.0.7",
-                "@types/valid-url": "^1.0.7",
-                "@types/webpack": "^5.28.5",
-                "@types/youtube": "0.0.50",
-                "chai": "^5.0.0",
-                "cross-env": "^7.0.3",
-                "eslint": "^8.57.0",
-                "eslint-config-airbnb": "^19.0.4",
-                "eslint-config-node": "^4.1.0",
-                "eslint-config-prettier": "^9.1.0",
-                "eslint-import-resolver-typescript": "^3.6.1",
-                "eslint-plugin-import": "^2.29.1",
-                "eslint-plugin-jsx-a11y": "^6.8.0",
-                "eslint-plugin-node": "^11.1.0",
-                "eslint-plugin-prettier": "^5.0.1",
-                "eslint-plugin-react": "^7.34.1",
-                "eslint-plugin-react-hooks": "^4.6.0",
-                "globals": "^15.1.0",
-                "jsdom": "^24.0.0",
-                "mocha": "^10.2.0",
-                "prettier": "^3.1.0",
-                "scss-loader": "0.0.1",
-                "style-loader": "^4.0.0",
-                "ts-loader": "^9.5.1",
-                "ts-node": "^10.9.1",
-                "ts-node-dev": "^2.0.0",
-                "typescript-eslint": "^7.8.0",
-                "webpack-dev-server": "^5.0.4"
-            },
-            "engines": {
-                "node": ">=12.0.0"
-            }
-        },
-        "node_modules/@adobe/react-spectrum": {
-            "version": "3.35.1",
-            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.35.1.tgz",
-            "integrity": "sha512-QNhsaEHv5S5Vqsk7b8aCV9F7qAnWw8VJ/Nep/SOjeiJ7vK993jEOetEhSsUIQ8VHsMKs6qkTtZr0/DKoV+Z/9w==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.3",
-                "@react-aria/i18n": "^3.11.1",
-                "@react-aria/ssr": "^3.9.4",
-                "@react-aria/utils": "^3.24.1",
-                "@react-aria/visually-hidden": "^3.8.12",
-                "@react-spectrum/actionbar": "^3.4.5",
-                "@react-spectrum/actiongroup": "^3.10.5",
-                "@react-spectrum/avatar": "^3.0.12",
-                "@react-spectrum/badge": "^3.1.13",
-                "@react-spectrum/breadcrumbs": "^3.9.7",
-                "@react-spectrum/button": "^3.16.4",
-                "@react-spectrum/buttongroup": "^3.6.13",
-                "@react-spectrum/calendar": "^3.4.9",
-                "@react-spectrum/checkbox": "^3.9.6",
-                "@react-spectrum/combobox": "^3.12.5",
-                "@react-spectrum/contextualhelp": "^3.6.11",
-                "@react-spectrum/datepicker": "^3.9.6",
-                "@react-spectrum/dialog": "^3.8.11",
-                "@react-spectrum/divider": "^3.5.13",
-                "@react-spectrum/dnd": "^3.3.10",
-                "@react-spectrum/dropzone": "^3.0.1",
-                "@react-spectrum/filetrigger": "^3.0.1",
-                "@react-spectrum/form": "^3.7.6",
-                "@react-spectrum/icon": "^3.7.13",
-                "@react-spectrum/illustratedmessage": "^3.5.1",
-                "@react-spectrum/image": "^3.5.1",
-                "@react-spectrum/inlinealert": "^3.2.5",
-                "@react-spectrum/labeledvalue": "^3.1.14",
-                "@react-spectrum/layout": "^3.6.5",
-                "@react-spectrum/link": "^3.6.7",
-                "@react-spectrum/list": "^3.7.10",
-                "@react-spectrum/listbox": "^3.12.9",
-                "@react-spectrum/menu": "^3.19.1",
-                "@react-spectrum/meter": "^3.5.1",
-                "@react-spectrum/numberfield": "^3.9.3",
-                "@react-spectrum/overlays": "^5.6.1",
-                "@react-spectrum/picker": "^3.14.5",
-                "@react-spectrum/progress": "^3.7.7",
-                "@react-spectrum/provider": "^3.9.7",
-                "@react-spectrum/radio": "^3.7.6",
-                "@react-spectrum/searchfield": "^3.8.6",
-                "@react-spectrum/slider": "^3.6.9",
-                "@react-spectrum/statuslight": "^3.5.13",
-                "@react-spectrum/switch": "^3.5.5",
-                "@react-spectrum/table": "^3.12.10",
-                "@react-spectrum/tabs": "^3.8.10",
-                "@react-spectrum/tag": "^3.2.6",
-                "@react-spectrum/text": "^3.5.5",
-                "@react-spectrum/textfield": "^3.12.1",
-                "@react-spectrum/theme-dark": "^3.5.10",
-                "@react-spectrum/theme-default": "^3.5.10",
-                "@react-spectrum/theme-light": "^3.4.10",
-                "@react-spectrum/tooltip": "^3.6.7",
-                "@react-spectrum/view": "^3.6.10",
-                "@react-spectrum/well": "^3.4.13",
-                "@react-stately/collections": "^3.10.7",
-                "@react-stately/data": "^3.11.4",
-                "@react-types/shared": "^3.23.1",
+    "dependencies": {
+        "@adobe/react-spectrum": {
+            "version": "3.35.0",
+            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.35.0.tgz",
+            "integrity": "sha512-dhCKWKDS/xF02EpWluicH7Va6KAANbrUx4rb1UeeFT3czhwz44oCQ74ZYviVedNv6Bq7O22/kbMa20rDdyt/4A==",
+            "requires": {
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/actionbar": "^3.4.4",
+                "@react-spectrum/actiongroup": "^3.10.4",
+                "@react-spectrum/avatar": "^3.0.11",
+                "@react-spectrum/badge": "^3.1.12",
+                "@react-spectrum/breadcrumbs": "^3.9.6",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/buttongroup": "^3.6.12",
+                "@react-spectrum/calendar": "^3.4.8",
+                "@react-spectrum/checkbox": "^3.9.5",
+                "@react-spectrum/combobox": "^3.12.4",
+                "@react-spectrum/contextualhelp": "^3.6.10",
+                "@react-spectrum/datepicker": "^3.9.5",
+                "@react-spectrum/dialog": "^3.8.10",
+                "@react-spectrum/divider": "^3.5.12",
+                "@react-spectrum/dnd": "^3.3.9",
+                "@react-spectrum/dropzone": "^3.0.0",
+                "@react-spectrum/filetrigger": "^3.0.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/icon": "^3.7.12",
+                "@react-spectrum/illustratedmessage": "^3.5.0",
+                "@react-spectrum/image": "^3.5.0",
+                "@react-spectrum/inlinealert": "^3.2.4",
+                "@react-spectrum/labeledvalue": "^3.1.13",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/link": "^3.6.6",
+                "@react-spectrum/list": "^3.7.9",
+                "@react-spectrum/listbox": "^3.12.8",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/meter": "^3.5.0",
+                "@react-spectrum/numberfield": "^3.9.2",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/picker": "^3.14.4",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/provider": "^3.9.6",
+                "@react-spectrum/radio": "^3.7.5",
+                "@react-spectrum/searchfield": "^3.8.5",
+                "@react-spectrum/slider": "^3.6.8",
+                "@react-spectrum/statuslight": "^3.5.12",
+                "@react-spectrum/switch": "^3.5.4",
+                "@react-spectrum/table": "^3.12.9",
+                "@react-spectrum/tabs": "^3.8.9",
+                "@react-spectrum/tag": "^3.2.5",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/theme-dark": "^3.5.9",
+                "@react-spectrum/theme-default": "^3.5.9",
+                "@react-spectrum/theme-light": "^3.4.9",
+                "@react-spectrum/tooltip": "^3.6.6",
+                "@react-spectrum/view": "^3.6.9",
+                "@react-spectrum/well": "^3.4.12",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/data": "^3.11.3",
+                "@react-types/shared": "^3.23.0",
                 "client-only": "^0.0.1"
             }
         },
@@ -1045,6 +733,13 @@
                 "@babel/helper-replace-supers": "^7.24.1",
                 "@babel/helper-split-export-declaration": "^7.24.5",
                 "globals": "^11.1.0"
+            },
+            "dependencies": {
+                "globals": {
+                    "version": "11.12.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+                    "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+                }
             }
         },
         "@babel/plugin-transform-computed-properties": {
@@ -1598,6 +1293,13 @@
                 "@babel/types": "^7.24.5",
                 "debug": "^4.3.1",
                 "globals": "^11.1.0"
+            },
+            "dependencies": {
+                "globals": {
+                    "version": "11.12.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+                    "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+                }
             }
         },
         "@babel/types": {
@@ -1610,6 +1312,11 @@
                 "to-fast-properties": "^2.0.0"
             }
         },
+        "@braintree/sanitize-url": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+            "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
+        },
         "@bundled-es-modules/pdfjs-dist": {
             "version": "3.6.172-alpha.1",
             "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-3.6.172-alpha.1.tgz",
@@ -1857,9 +1564,9 @@
             }
         },
         "@eslint/js": {
-            "version": "8.57.0",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
-            "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+            "version": "9.4.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.4.0.tgz",
+            "integrity": "sha512-fdI7VJjP3Rvc70lC4xkFXHB0fiPeojiL1PxVG6t1ZvXQrarj893PweuBTujxDUFk0Fxj4R7PIIAZ/aiiyZPZcg==",
             "dev": true
         },
         "@ffmpeg/core": {
@@ -2233,6 +1940,72 @@
                 }
             }
         },
+        "@jest/schemas": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+            "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+            "requires": {
+                "@sinclair/typebox": "^0.27.8"
+            }
+        },
+        "@jest/types": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+            "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+            "requires": {
+                "@jest/schemas": "^29.6.3",
+                "@types/istanbul-lib-coverage": "^2.0.0",
+                "@types/istanbul-reports": "^3.0.0",
+                "@types/node": "*",
+                "@types/yargs": "^17.0.8",
+                "chalk": "^4.0.0"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "4.1.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                    "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "supports-color": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                    "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "@jimp/bmp": {
             "version": "0.22.12",
             "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
@@ -2889,7 +2662,6 @@
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
             "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-            "dev": true,
             "requires": {
                 "@nodelib/fs.stat": "2.0.5",
                 "run-parallel": "^1.1.9"
@@ -2898,14 +2670,12 @@
         "@nodelib/fs.stat": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-            "dev": true
+            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
         },
         "@nodelib/fs.walk": {
             "version": "1.2.8",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
             "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-            "dev": true,
             "requires": {
                 "@nodelib/fs.scandir": "2.1.5",
                 "fastq": "^1.6.0"
@@ -4592,6 +4362,49 @@
                 "@swc/helpers": "^0.5.0"
             }
         },
+        "@react-spring/animated": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz",
+            "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==",
+            "requires": {
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/core": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz",
+            "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==",
+            "requires": {
+                "@react-spring/animated": "~9.7.3",
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/shared": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz",
+            "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==",
+            "requires": {
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/types": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz",
+            "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw=="
+        },
+        "@react-spring/web": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz",
+            "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==",
+            "requires": {
+                "@react-spring/animated": "~9.7.3",
+                "@react-spring/core": "~9.7.3",
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
         "@react-stately/calendar": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.5.0.tgz",
@@ -5305,6 +5118,11 @@
                 "selderee": "^0.11.0"
             }
         },
+        "@sinclair/typebox": {
+            "version": "0.27.8",
+            "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+            "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
+        },
         "@sindresorhus/is": {
             "version": "6.3.0",
             "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.0.tgz",
@@ -7004,8 +6822,7 @@
         "@types/d3-scale-chromatic": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
-            "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==",
-            "dev": true
+            "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
         },
         "@types/d3-selection": {
             "version": "3.0.10",
@@ -7169,9 +6986,9 @@
             }
         },
         "@types/geojson": {
-            "version": "7946.0.8",
-            "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
-            "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+            "version": "7946.0.14",
+            "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
+            "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
         },
         "@types/glob": {
             "version": "7.2.0",
@@ -7241,6 +7058,27 @@
                 "@types/node": "*"
             }
         },
+        "@types/istanbul-lib-coverage": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+            "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
+        },
+        "@types/istanbul-lib-report": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+            "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+            "requires": {
+                "@types/istanbul-lib-coverage": "*"
+            }
+        },
+        "@types/istanbul-reports": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+            "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+            "requires": {
+                "@types/istanbul-lib-report": "*"
+            }
+        },
         "@types/jquery": {
             "version": "3.5.30",
             "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz",
@@ -7271,6 +7109,11 @@
             "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
             "dev": true
         },
+        "@types/katex": {
+            "version": "0.16.7",
+            "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
+            "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
+        },
         "@types/keygrip": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
@@ -7730,6 +7573,11 @@
             "integrity": "sha512-tgsWVG80dM5PVEBSbXUttPJTBCOo0IKbBh4R4z/SHsC5C81A3aaUH4fsbj+JYk7fopApU/Mao1c0EWTE592TSg==",
             "dev": true
         },
+        "@types/web": {
+            "version": "0.0.148",
+            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.148.tgz",
+            "integrity": "sha512-HX2eARbn26tZuCOxZ25Ew6UUNhw8fgdGrOGcxX0/J6yTtlJm+nHlL9/h+2zgSzse13vlVe+c+W3LWqhnlAd5rg=="
+        },
         "@types/webidl-conversions": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -7773,12 +7621,123 @@
                 "@types/node": "*"
             }
         },
+        "@types/yargs": {
+            "version": "17.0.32",
+            "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+            "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+            "requires": {
+                "@types/yargs-parser": "*"
+            }
+        },
+        "@types/yargs-parser": {
+            "version": "21.0.3",
+            "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+            "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
+        },
         "@types/youtube": {
             "version": "0.0.50",
             "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.0.50.tgz",
             "integrity": "sha512-d4GpH4uPYp9W07kc487tiq6V/EUHl18vZWFMbQoe4Sk9LXEWzFi/BMf9x7TI4m7/j7gU3KeX8H6M8aPBgykeLw==",
             "dev": true
         },
+        "@typescript-eslint/eslint-plugin": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz",
+            "integrity": "sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/type-utils": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.3.1",
+                "natural-compare": "^1.4.0",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/parser": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.0.tgz",
+            "integrity": "sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==",
+            "requires": {
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/typescript-estree": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "debug": "^4.3.4"
+            }
+        },
+        "@typescript-eslint/scope-manager": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz",
+            "integrity": "sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0"
+            }
+        },
+        "@typescript-eslint/type-utils": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz",
+            "integrity": "sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/typescript-estree": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0",
+                "debug": "^4.3.4",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/types": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.0.tgz",
+            "integrity": "sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA=="
+        },
+        "@typescript-eslint/typescript-estree": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz",
+            "integrity": "sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "debug": "^4.3.4",
+                "globby": "^11.1.0",
+                "is-glob": "^4.0.3",
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.6.2",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+                    "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
+                }
+            }
+        },
+        "@typescript-eslint/utils": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.0.tgz",
+            "integrity": "sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/eslint-utils": "^4.4.0",
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/typescript-estree": "7.13.0"
+            }
+        },
+        "@typescript-eslint/visitor-keys": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz",
+            "integrity": "sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "eslint-visitor-keys": "^3.4.3"
+            }
+        },
         "@ungap/structured-clone": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -8681,6 +8640,14 @@
             "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
             "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
         },
+        "better-react-mathjax": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz",
+            "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==",
+            "requires": {
+                "mathjax-full": "^3.2.2"
+            }
+        },
         "bezier-curve": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz",
@@ -8957,6 +8924,11 @@
                                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
                                     "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
                                 },
+                                "ansi-styles": {
+                                    "version": "6.2.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+                                    "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
+                                },
                                 "emoji-regex": {
                                     "version": "9.2.2",
                                     "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -8979,6 +8951,16 @@
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
+                                },
+                                "wrap-ansi": {
+                                    "version": "8.1.0",
+                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+                                    "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+                                    "requires": {
+                                        "ansi-styles": "^6.1.0",
+                                        "string-width": "^5.0.1",
+                                        "strip-ansi": "^7.0.1"
+                                    }
                                 }
                             }
                         },
@@ -10829,7 +10811,7 @@
                             }
                         },
                         "string-width": {
-                            "version": "npm:string-width@4.2.3",
+                            "version": "4.2.3",
                             "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
                             "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
                             "requires": {
@@ -10847,7 +10829,7 @@
                             }
                         },
                         "strip-ansi": {
-                            "version": "npm:strip-ansi@6.0.1",
+                            "version": "6.0.1",
                             "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
                             "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
                             "requires": {
@@ -11367,6 +11349,11 @@
             "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
             "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
         },
+        "ci-info": {
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+            "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
+        },
         "class-transformer": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
@@ -11706,6 +11693,35 @@
                 }
             }
         },
+        "compute-cosine-similarity": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-cosine-similarity/-/compute-cosine-similarity-1.1.0.tgz",
+            "integrity": "sha512-FXhNx0ILLjGi9Z9+lglLzM12+0uoTnYkHm7GiadXDAr0HGVLm25OivUS1B/LPkbzzvlcXz/1EvWg9ZYyJSdhTw==",
+            "requires": {
+                "compute-dot": "^1.1.0",
+                "compute-l2norm": "^1.1.0",
+                "validate.io-array": "^1.0.5",
+                "validate.io-function": "^1.0.2"
+            }
+        },
+        "compute-dot": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-dot/-/compute-dot-1.1.0.tgz",
+            "integrity": "sha512-L5Ocet4DdMrXboss13K59OK23GXjiSia7+7Ukc7q4Bl+RVpIXK2W9IHMbWDZkh+JUEvJAwOKRaJDiFUa1LTnJg==",
+            "requires": {
+                "validate.io-array": "^1.0.3",
+                "validate.io-function": "^1.0.2"
+            }
+        },
+        "compute-l2norm": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-l2norm/-/compute-l2norm-1.1.0.tgz",
+            "integrity": "sha512-6EHh1Elj90eU28SXi+h2PLnTQvZmkkHWySpoFz+WOlVNLz3DQoC4ISUHSV9n5jMxPHtKGJ01F4uu2PsXBB8sSg==",
+            "requires": {
+                "validate.io-array": "^1.0.3",
+                "validate.io-function": "^1.0.2"
+            }
+        },
         "concat-map": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -11874,6 +11890,14 @@
                 "vary": "^1"
             }
         },
+        "cose-base": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+            "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+            "requires": {
+                "layout-base": "^1.0.0"
+            }
+        },
         "cosmiconfig": {
             "version": "7.1.0",
             "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -12059,11 +12083,44 @@
             "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz",
             "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag=="
         },
+        "csvtojson": {
+            "version": "2.0.10",
+            "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
+            "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
+            "requires": {
+                "bluebird": "^3.5.1",
+                "lodash": "^4.17.3",
+                "strip-bom": "^2.0.0"
+            },
+            "dependencies": {
+                "strip-bom": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+                    "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==",
+                    "requires": {
+                        "is-utf8": "^0.2.0"
+                    }
+                }
+            }
+        },
         "custom-event": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
             "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
         },
+        "cytoscape": {
+            "version": "3.29.2",
+            "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.29.2.tgz",
+            "integrity": "sha512-2G1ycU28Nh7OHT9rkXRLpCDP30MKH1dXJORZuBhtEhEW7pKwgPi77ImqlCWinouyE1PNepIOGZBOrE84DG7LyQ=="
+        },
+        "cytoscape-cose-bilkent": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+            "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+            "requires": {
+                "cose-base": "^1.0.0"
+            }
+        },
         "d3": {
             "version": "7.9.0",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
@@ -12288,6 +12345,30 @@
             "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
             "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="
         },
+        "d3-sankey": {
+            "version": "0.12.3",
+            "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+            "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+            "requires": {
+                "d3-array": "1 - 2",
+                "d3-shape": "^1.2.0"
+            },
+            "dependencies": {
+                "d3-path": {
+                    "version": "1.0.9",
+                    "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+                    "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
+                },
+                "d3-shape": {
+                    "version": "1.3.7",
+                    "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+                    "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+                    "requires": {
+                        "d3-path": "1"
+                    }
+                }
+            }
+        },
         "d3-scale": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
@@ -12392,6 +12473,15 @@
                 "d3-transition": "2 - 3"
             }
         },
+        "dagre-d3-es": {
+            "version": "7.0.10",
+            "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz",
+            "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==",
+            "requires": {
+                "d3": "^7.8.2",
+                "lodash-es": "^4.17.21"
+            }
+        },
         "damerau-levenshtein": {
             "version": "1.0.8",
             "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -12486,6 +12576,11 @@
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
             "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="
         },
+        "dayjs": {
+            "version": "1.11.11",
+            "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
+            "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+        },
         "debug": {
             "version": "4.3.4",
             "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12764,6 +12859,14 @@
             "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
             "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
         },
+        "dir-glob": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+            "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+            "requires": {
+                "path-type": "^4.0.0"
+            }
+        },
         "dns-packet": {
             "version": "5.6.1",
             "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -12832,6 +12935,11 @@
                 "domelementtype": "^2.3.0"
             }
         },
+        "dompurify": {
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz",
+            "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA=="
+        },
         "domutils": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@@ -12907,6 +13015,11 @@
             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.761.tgz",
             "integrity": "sha512-PIbxpiJGx6Bb8dQaonNc6CGTRlVntdLg/2nMa1YhnrwYOORY9a3ZgGN0UQYE6lAcj/lkyduJN7BPt/JiY+jAQQ=="
         },
+        "elkjs": {
+            "version": "0.9.3",
+            "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
+            "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="
+        },
         "emoji-regex": {
             "version": "9.2.2",
             "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -13183,6 +13296,12 @@
                 "text-table": "^0.2.0"
             },
             "dependencies": {
+                "@eslint/js": {
+                    "version": "8.57.0",
+                    "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+                    "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+                    "dev": true
+                },
                 "ajv": {
                     "version": "6.12.6",
                     "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -14045,6 +14164,21 @@
                 }
             }
         },
+        "eslint-import-resolver-typescript": {
+            "version": "3.6.1",
+            "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz",
+            "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==",
+            "dev": true,
+            "requires": {
+                "debug": "^4.3.4",
+                "enhanced-resolve": "^5.12.0",
+                "eslint-module-utils": "^2.7.4",
+                "fast-glob": "^3.3.1",
+                "get-tsconfig": "^4.5.0",
+                "is-core-module": "^2.11.0",
+                "is-glob": "^4.0.3"
+            }
+        },
         "eslint-module-utils": {
             "version": "2.8.1",
             "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
@@ -14390,8 +14524,50 @@
         "eslint-visitor-keys": {
             "version": "3.4.3",
             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
-            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-            "dev": true
+            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
+        },
+        "eslint-webpack-plugin": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.2.0.tgz",
+            "integrity": "sha512-rsfpFQ01AWQbqtjgPRr2usVRxhWDuG0YDYcG8DJOteD3EFnpeuYuOwk0PQiN7PRBTqS6ElNdtPZPggj8If9WnA==",
+            "requires": {
+                "@types/eslint": "^8.56.10",
+                "jest-worker": "^29.7.0",
+                "micromatch": "^4.0.5",
+                "normalize-path": "^3.0.0",
+                "schema-utils": "^4.2.0"
+            },
+            "dependencies": {
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "jest-worker": {
+                    "version": "29.7.0",
+                    "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+                    "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+                    "requires": {
+                        "@types/node": "*",
+                        "jest-util": "^29.7.0",
+                        "merge-stream": "^2.0.0",
+                        "supports-color": "^8.0.0"
+                    }
+                },
+                "supports-color": {
+                    "version": "8.1.1",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+                    "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "esm": {
+            "version": "3.2.25",
+            "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+            "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
         },
         "espree": {
             "version": "9.6.1",
@@ -14702,6 +14878,11 @@
                 "tmp": "^0.0.33"
             }
         },
+        "extract-colors": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/extract-colors/-/extract-colors-4.0.6.tgz",
+            "integrity": "sha512-U+pYyQKXCSHOmtZPIEJBGLJjLDiqS+oOub2ILA3a7UGt9+IvZvwAN3hOPFjUgT+gX/apSBwP5vBgnKMlV0fy8Q=="
+        },
         "extsprintf": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -14728,6 +14909,18 @@
             "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
             "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
         },
+        "fast-glob": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+            "requires": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            }
+        },
         "fast-json-stable-stringify": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -14748,7 +14941,6 @@
             "version": "1.17.1",
             "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
             "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
-            "dev": true,
             "requires": {
                 "reusify": "^1.0.4"
             }
@@ -15491,6 +15683,13 @@
                 "@turf/meta": "6.x",
                 "@types/geojson": "7946.0.8",
                 "rbush": "^3.0.1"
+            },
+            "dependencies": {
+                "@types/geojson": {
+                    "version": "7946.0.8",
+                    "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
+                    "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+                }
             }
         },
         "geojson-vt": {
@@ -15552,6 +15751,15 @@
                 "get-intrinsic": "^1.2.4"
             }
         },
+        "get-tsconfig": {
+            "version": "4.7.5",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+            "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
+            "dev": true,
+            "requires": {
+                "resolve-pkg-maps": "^1.0.0"
+            }
+        },
         "get-value": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -15646,9 +15854,10 @@
             }
         },
         "globals": {
-            "version": "11.12.0",
-            "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-            "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+            "version": "15.4.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-15.4.0.tgz",
+            "integrity": "sha512-unnwvMZpv0eDUyjNyh9DH/yxUaRYrEjW/qK4QcdrHg3oO11igUQrCSgODHEqxlKg8v2CD2Sd7UkqqEBoz5U7TQ==",
+            "dev": true
         },
         "globalthis": {
             "version": "1.0.4",
@@ -15660,6 +15869,19 @@
                 "gopd": "^1.0.1"
             }
         },
+        "globby": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+            "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+            "requires": {
+                "array-union": "^2.1.0",
+                "dir-glob": "^3.0.1",
+                "fast-glob": "^3.2.9",
+                "ignore": "^5.2.0",
+                "merge2": "^1.4.1",
+                "slash": "^3.0.0"
+            }
+        },
         "golden-layout": {
             "version": "2.6.0",
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz",
@@ -15865,6 +16087,40 @@
                 "function-bind": "^1.1.2"
             }
         },
+        "hast-util-from-dom": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz",
+            "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "hastscript": "^8.0.0",
+                "web-namespaces": "^2.0.0"
+            }
+        },
+        "hast-util-from-html": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz",
+            "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "devlop": "^1.1.0",
+                "hast-util-from-parse5": "^8.0.0",
+                "parse5": "^7.0.0",
+                "vfile": "^6.0.0",
+                "vfile-message": "^4.0.0"
+            }
+        },
+        "hast-util-from-html-isomorphic": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+            "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "hast-util-from-dom": "^5.0.0",
+                "hast-util-from-html": "^2.0.0",
+                "unist-util-remove-position": "^5.0.0"
+            }
+        },
         "hast-util-from-parse5": {
             "version": "8.0.1",
             "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz",
@@ -15880,6 +16136,14 @@
                 "web-namespaces": "^2.0.0"
             }
         },
+        "hast-util-is-element": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+            "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+            "requires": {
+                "@types/hast": "^3.0.0"
+            }
+        },
         "hast-util-parse-selector": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
@@ -15944,6 +16208,17 @@
                 "zwitch": "^2.0.0"
             }
         },
+        "hast-util-to-text": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+            "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/unist": "^3.0.0",
+                "hast-util-is-element": "^3.0.0",
+                "unist-util-find-after": "^5.0.0"
+            }
+        },
         "hast-util-whitespace": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
@@ -17167,6 +17442,11 @@
             "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
             "dev": true
         },
+        "is-utf8": {
+            "version": "0.2.1",
+            "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+            "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
+        },
         "is-weakmap": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
@@ -17266,6 +17546,64 @@
                 "@pkgjs/parseargs": "^0.11.0"
             }
         },
+        "jest-util": {
+            "version": "29.7.0",
+            "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+            "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+            "requires": {
+                "@jest/types": "^29.6.3",
+                "@types/node": "*",
+                "chalk": "^4.0.0",
+                "ci-info": "^3.2.0",
+                "graceful-fs": "^4.2.9",
+                "picomatch": "^2.2.3"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "4.1.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                    "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "supports-color": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                    "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "jest-worker": {
             "version": "27.5.1",
             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -17623,6 +17961,21 @@
             "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
             "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q=="
         },
+        "katex": {
+            "version": "0.16.10",
+            "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
+            "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
+            "requires": {
+                "commander": "^8.3.0"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "8.3.0",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+                    "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
+                }
+            }
+        },
         "kdbush": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
@@ -17644,6 +17997,11 @@
                 "json-buffer": "3.0.1"
             }
         },
+        "khroma": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+            "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+        },
         "kind-of": {
             "version": "6.0.3",
             "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -17657,6 +18015,11 @@
                 "graceful-fs": "^4.1.9"
             }
         },
+        "kleur": {
+            "version": "4.1.5",
+            "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+            "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
+        },
         "kruptein": {
             "version": "3.0.6",
             "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz",
@@ -17715,6 +18078,11 @@
                 "shell-quote": "^1.8.1"
             }
         },
+        "layout-base": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+            "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
+        },
         "lazystream": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
@@ -17739,7 +18107,12 @@
                 }
             }
         },
-        "node_modules/leac": {
+        "ldrs": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.0.2.tgz",
+            "integrity": "sha512-sYJmivdkIiHrUEqTrEWccBoLdaENpzbzkABI5rk8rRxTXrg9i2xVuDvUUuhOhJY3RmQyaoxs046pM1DCRdcIpg=="
+        },
+        "leac": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
             "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="
@@ -17767,6 +18140,14 @@
             "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
             "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
         },
+        "linkify-it": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+            "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+            "requires": {
+                "uc.micro": "^2.0.0"
+            }
+        },
         "load-bmfont": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz",
@@ -18058,6 +18439,26 @@
                 "vt-pbf": "^3.1.3"
             }
         },
+        "markdown-it": {
+            "version": "14.1.0",
+            "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+            "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+            "requires": {
+                "argparse": "^2.0.1",
+                "entities": "^4.4.0",
+                "linkify-it": "^5.0.0",
+                "mdurl": "^2.0.0",
+                "punycode.js": "^2.3.1",
+                "uc.micro": "^2.1.0"
+            },
+            "dependencies": {
+                "argparse": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+                    "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+                }
+            }
+        },
         "markdown-table": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -18077,6 +18478,17 @@
                 "mr-parser": "^0.2.1"
             }
         },
+        "mathjax-full": {
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
+            "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
+            "requires": {
+                "esm": "^3.2.25",
+                "mhchemparser": "^4.1.0",
+                "mj-context-menu": "^0.6.1",
+                "speech-rule-engine": "^4.0.6"
+            }
+        },
         "mathquill": {
             "version": "0.10.1-a",
             "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
@@ -18205,6 +18617,20 @@
                 "mdast-util-to-markdown": "^2.0.0"
             }
         },
+        "mdast-util-math": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+            "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/mdast": "^4.0.0",
+                "devlop": "^1.0.0",
+                "longest-streak": "^3.0.0",
+                "mdast-util-from-markdown": "^2.0.0",
+                "mdast-util-to-markdown": "^2.1.0",
+                "unist-util-remove-position": "^5.0.0"
+            }
+        },
         "mdast-util-mdx-expression": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
@@ -18299,6 +18725,11 @@
                 "@types/mdast": "^4.0.0"
             }
         },
+        "mdurl": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+            "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
+        },
         "media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -18337,11 +18768,309 @@
             "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
             "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
         },
+        "merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+        },
+        "mermaid": {
+            "version": "10.9.1",
+            "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.1.tgz",
+            "integrity": "sha512-Mx45Obds5W1UkW1nv/7dHRsbfMM1aOKA2+Pxs/IGHNonygDHwmng8xTHyS9z4KWVi0rbko8gjiBmuwwXQ7tiNA==",
+            "requires": {
+                "@braintree/sanitize-url": "^6.0.1",
+                "@types/d3-scale": "^4.0.3",
+                "@types/d3-scale-chromatic": "^3.0.0",
+                "cytoscape": "^3.28.1",
+                "cytoscape-cose-bilkent": "^4.1.0",
+                "d3": "^7.4.0",
+                "d3-sankey": "^0.12.3",
+                "dagre-d3-es": "7.0.10",
+                "dayjs": "^1.11.7",
+                "dompurify": "^3.0.5",
+                "elkjs": "^0.9.0",
+                "katex": "^0.16.9",
+                "khroma": "^2.0.0",
+                "lodash-es": "^4.17.21",
+                "mdast-util-from-markdown": "^1.3.0",
+                "non-layered-tidy-tree-layout": "^2.0.2",
+                "stylis": "^4.1.3",
+                "ts-dedent": "^2.2.0",
+                "uuid": "^9.0.0",
+                "web-worker": "^1.2.0"
+            },
+            "dependencies": {
+                "@types/mdast": {
+                    "version": "3.0.15",
+                    "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+                    "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+                    "requires": {
+                        "@types/unist": "^2"
+                    }
+                },
+                "@types/unist": {
+                    "version": "2.0.10",
+                    "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
+                    "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
+                },
+                "mdast-util-from-markdown": {
+                    "version": "1.3.1",
+                    "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
+                    "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
+                    "requires": {
+                        "@types/mdast": "^3.0.0",
+                        "@types/unist": "^2.0.0",
+                        "decode-named-character-reference": "^1.0.0",
+                        "mdast-util-to-string": "^3.1.0",
+                        "micromark": "^3.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-decode-string": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "unist-util-stringify-position": "^3.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "mdast-util-to-string": {
+                    "version": "3.2.0",
+                    "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
+                    "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
+                    "requires": {
+                        "@types/mdast": "^3.0.0"
+                    }
+                },
+                "micromark": {
+                    "version": "3.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
+                    "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
+                    "requires": {
+                        "@types/debug": "^4.0.0",
+                        "debug": "^4.0.0",
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-core-commonmark": "^1.0.1",
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-combine-extensions": "^1.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-encode": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-resolve-all": "^1.0.0",
+                        "micromark-util-sanitize-uri": "^1.0.0",
+                        "micromark-util-subtokenize": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.1",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-core-commonmark": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
+                    "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
+                    "requires": {
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-factory-destination": "^1.0.0",
+                        "micromark-factory-label": "^1.0.0",
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-factory-title": "^1.0.0",
+                        "micromark-factory-whitespace": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-classify-character": "^1.0.0",
+                        "micromark-util-html-tag-name": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-resolve-all": "^1.0.0",
+                        "micromark-util-subtokenize": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.1",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-factory-destination": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
+                    "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-label": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
+                    "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-factory-space": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
+                    "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-title": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
+                    "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
+                    "requires": {
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-whitespace": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
+                    "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
+                    "requires": {
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-character": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
+                    "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-chunked": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
+                    "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-classify-character": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
+                    "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-combine-extensions": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
+                    "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
+                    "requires": {
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-decode-numeric-character-reference": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
+                    "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-decode-string": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
+                    "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
+                    "requires": {
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-encode": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
+                    "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="
+                },
+                "micromark-util-html-tag-name": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
+                    "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="
+                },
+                "micromark-util-normalize-identifier": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
+                    "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-resolve-all": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
+                    "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
+                    "requires": {
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-sanitize-uri": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
+                    "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-encode": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-subtokenize": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
+                    "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
+                    "requires": {
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-util-symbol": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
+                    "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="
+                },
+                "micromark-util-types": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
+                    "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="
+                },
+                "unist-util-stringify-position": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
+                    "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
+                    "requires": {
+                        "@types/unist": "^2.0.0"
+                    }
+                }
+            }
+        },
         "methods": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
             "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
         },
+        "mhchemparser": {
+            "version": "4.2.1",
+            "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz",
+            "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
+        },
         "micromark": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
@@ -18475,6 +19204,20 @@
                 "micromark-util-types": "^2.0.0"
             }
         },
+        "micromark-extension-math": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz",
+            "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==",
+            "requires": {
+                "@types/katex": "^0.16.0",
+                "devlop": "^1.0.0",
+                "katex": "^0.16.0",
+                "micromark-factory-space": "^2.0.0",
+                "micromark-util-character": "^2.0.0",
+                "micromark-util-symbol": "^2.0.0",
+                "micromark-util-types": "^2.0.0"
+            }
+        },
         "micromark-factory-destination": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
@@ -18736,6 +19479,11 @@
                 }
             }
         },
+        "mj-context-menu": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
+            "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
+        },
         "mkdirp": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -19044,6 +19792,11 @@
             "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz",
             "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw=="
         },
+        "mri": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+            "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
+        },
         "ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -19102,9 +19855,9 @@
             "dev": true
         },
         "nan": {
-            "version": "2.19.0",
-            "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
-            "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
+            "version": "2.20.0",
+            "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
+            "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
             "optional": true
         },
         "nanoid": {
@@ -19256,6 +20009,11 @@
                 }
             }
         },
+        "non-layered-tidy-tree-layout": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+            "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
+        },
         "nopt": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -19352,7 +20110,7 @@
             "dependencies": {
                 "@isaacs/cliui": {
                     "version": "8.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "string-width": "^5.1.2",
                         "string-width-cjs": "npm:string-width@^4.2.0",
@@ -19364,7 +20122,7 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "6.0.1",
-                            "bundled": true
+                            "resolved": false
                         },
                         "ansi-styles": {
                             "version": "4.3.0",
@@ -19376,11 +20134,11 @@
                         },
                         "emoji-regex": {
                             "version": "9.2.2",
-                            "bundled": true
+                            "resolved": false
                         },
                         "string-width": {
                             "version": "5.1.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "eastasianwidth": "^0.2.0",
                                 "emoji-regex": "^9.2.2",
@@ -19419,7 +20177,7 @@
                         },
                         "strip-ansi": {
                             "version": "7.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "ansi-regex": "^6.0.1"
                             }
@@ -19483,11 +20241,11 @@
                 },
                 "@isaacs/string-locale-compare": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/agent": {
                     "version": "2.2.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.0",
                         "http-proxy-agent": "^7.0.0",
@@ -19498,7 +20256,7 @@
                 },
                 "@npmcli/arborist": {
                     "version": "7.5.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@isaacs/string-locale-compare": "^1.1.0",
                         "@npmcli/fs": "^3.1.1",
@@ -19539,7 +20297,7 @@
                 },
                 "@npmcli/config": {
                     "version": "8.3.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/map-workspaces": "^3.0.2",
                         "ci-info": "^4.0.0",
@@ -19553,14 +20311,14 @@
                 },
                 "@npmcli/fs": {
                     "version": "3.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "semver": "^7.3.5"
                     }
                 },
                 "@npmcli/git": {
                     "version": "5.0.7",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/promise-spawn": "^7.0.0",
                         "lru-cache": "^10.0.1",
@@ -19574,7 +20332,7 @@
                 },
                 "@npmcli/installed-package-contents": {
                     "version": "2.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-bundled": "^3.0.0",
                         "npm-normalize-package-bin": "^3.0.0"
@@ -19582,7 +20340,7 @@
                 },
                 "@npmcli/map-workspaces": {
                     "version": "3.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/name-from-folder": "^2.0.0",
                         "glob": "^10.2.2",
@@ -19592,7 +20350,7 @@
                 },
                 "@npmcli/metavuln-calculator": {
                     "version": "7.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cacache": "^18.0.0",
                         "json-parse-even-better-errors": "^3.0.0",
@@ -19603,15 +20361,15 @@
                 },
                 "@npmcli/name-from-folder": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/node-gyp": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/package-json": {
                     "version": "5.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.0",
                         "glob": "^10.2.2",
@@ -19624,25 +20382,25 @@
                 },
                 "@npmcli/promise-spawn": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "which": "^4.0.0"
                     }
                 },
                 "@npmcli/query": {
                     "version": "3.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "postcss-selector-parser": "^6.0.10"
                     }
                 },
                 "@npmcli/redact": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/run-script": {
                     "version": "8.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/node-gyp": "^3.0.0",
                         "@npmcli/package-json": "^5.0.0",
@@ -19654,27 +20412,27 @@
                 },
                 "@pkgjs/parseargs": {
                     "version": "0.11.0",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true
                 },
                 "@sigstore/bundle": {
                     "version": "2.3.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/protobuf-specs": "^0.3.2"
                     }
                 },
                 "@sigstore/core": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@sigstore/protobuf-specs": {
                     "version": "0.3.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@sigstore/sign": {
                     "version": "2.3.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.0.0",
@@ -19686,7 +20444,7 @@
                 },
                 "@sigstore/tuf": {
                     "version": "2.3.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/protobuf-specs": "^0.3.2",
                         "tuf-js": "^2.2.1"
@@ -19694,7 +20452,7 @@
                 },
                 "@sigstore/verify": {
                     "version": "1.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.1.0",
@@ -19703,11 +20461,11 @@
                 },
                 "@tufjs/canonical-json": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@tufjs/models": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@tufjs/canonical-json": "2.0.0",
                         "minimatch": "^9.0.4"
@@ -19715,18 +20473,18 @@
                 },
                 "abbrev": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "agent-base": {
                     "version": "7.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "debug": "^4.3.4"
                     }
                 },
                 "aggregate-error": {
                     "version": "3.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "clean-stack": "^2.0.0",
                         "indent-string": "^4.0.0"
@@ -19734,27 +20492,27 @@
                 },
                 "ansi-regex": {
                     "version": "5.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ansi-styles": {
                     "version": "6.2.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "aproba": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "archy": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "balanced-match": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "bin-links": {
                     "version": "4.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cmd-shim": "^6.0.0",
                         "npm-normalize-package-bin": "^3.0.0",
@@ -19764,18 +20522,18 @@
                 },
                 "binary-extensions": {
                     "version": "2.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "brace-expansion": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "balanced-match": "^1.0.0"
                     }
                 },
                 "cacache": {
                     "version": "18.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/fs": "^3.1.0",
                         "fs-minipass": "^3.0.0",
@@ -19793,30 +20551,30 @@
                 },
                 "chalk": {
                     "version": "5.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "chownr": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ci-info": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cidr-regex": {
                     "version": "4.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ip-regex": "^5.0.0"
                     }
                 },
                 "clean-stack": {
                     "version": "2.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cli-columns": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "string-width": "^4.2.3",
                         "strip-ansi": "^6.0.1"
@@ -19824,26 +20582,26 @@
                 },
                 "cmd-shim": {
                     "version": "6.0.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "color-convert": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "color-name": "~1.1.4"
                     }
                 },
                 "color-name": {
                     "version": "1.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "common-ancestor-path": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cross-spawn": {
                     "version": "7.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "path-key": "^3.1.0",
                         "shebang-command": "^2.0.0",
@@ -19852,7 +20610,7 @@
                     "dependencies": {
                         "which": {
                             "version": "2.0.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "isexe": "^2.0.0"
                             }
@@ -19861,36 +20619,36 @@
                 },
                 "cssesc": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "debug": {
                     "version": "4.3.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ms": "2.1.2"
                     },
                     "dependencies": {
                         "ms": {
                             "version": "2.1.2",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "diff": {
                     "version": "5.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "eastasianwidth": {
                     "version": "0.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "emoji-regex": {
                     "version": "8.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "encoding": {
                     "version": "0.1.13",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true,
                     "requires": {
                         "iconv-lite": "^0.6.2"
@@ -19898,23 +20656,23 @@
                 },
                 "env-paths": {
                     "version": "2.2.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "err-code": {
                     "version": "2.0.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "exponential-backoff": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "fastest-levenshtein": {
                     "version": "1.0.16",
-                    "bundled": true
+                    "resolved": false
                 },
                 "foreground-child": {
                     "version": "3.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cross-spawn": "^7.0.0",
                         "signal-exit": "^4.0.1"
@@ -19922,18 +20680,18 @@
                 },
                 "fs-minipass": {
                     "version": "3.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "function-bind": {
                     "version": "1.1.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "glob": {
                     "version": "10.4.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "foreground-child": "^3.1.0",
                         "jackspeak": "^3.1.2",
@@ -19944,29 +20702,29 @@
                 },
                 "graceful-fs": {
                     "version": "4.2.11",
-                    "bundled": true
+                    "resolved": false
                 },
                 "hasown": {
                     "version": "2.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "function-bind": "^1.1.2"
                     }
                 },
                 "hosted-git-info": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "lru-cache": "^10.0.1"
                     }
                 },
                 "http-cache-semantics": {
                     "version": "4.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "http-proxy-agent": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.0",
                         "debug": "^4.3.4"
@@ -19974,7 +20732,7 @@
                 },
                 "https-proxy-agent": {
                     "version": "7.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.0.2",
                         "debug": "4"
@@ -19982,7 +20740,7 @@
                 },
                 "iconv-lite": {
                     "version": "0.6.3",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true,
                     "requires": {
                         "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -19990,26 +20748,26 @@
                 },
                 "ignore-walk": {
                     "version": "6.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minimatch": "^9.0.0"
                     }
                 },
                 "imurmurhash": {
                     "version": "0.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "indent-string": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ini": {
                     "version": "4.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "init-package-json": {
                     "version": "6.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/package-json": "^5.0.0",
                         "npm-package-arg": "^11.0.0",
@@ -20022,7 +20780,7 @@
                 },
                 "ip-address": {
                     "version": "9.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "jsbn": "1.1.0",
                         "sprintf-js": "^1.1.3"
@@ -20030,37 +20788,37 @@
                 },
                 "ip-regex": {
                     "version": "5.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "is-cidr": {
                     "version": "5.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cidr-regex": "^4.1.1"
                     }
                 },
                 "is-core-module": {
                     "version": "2.13.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hasown": "^2.0.0"
                     }
                 },
                 "is-fullwidth-code-point": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "is-lambda": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "isexe": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "jackspeak": {
                     "version": "3.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@isaacs/cliui": "^8.0.2",
                         "@pkgjs/parseargs": "^0.11.0"
@@ -20068,31 +20826,31 @@
                 },
                 "jsbn": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "json-parse-even-better-errors": {
                     "version": "3.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "json-stringify-nice": {
                     "version": "1.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "jsonparse": {
                     "version": "1.3.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "just-diff": {
                     "version": "6.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "just-diff-apply": {
                     "version": "5.5.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "libnpmaccess": {
                     "version": "8.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-package-arg": "^11.0.2",
                         "npm-registry-fetch": "^17.0.1"
@@ -20100,7 +20858,7 @@
                 },
                 "libnpmdiff": {
                     "version": "6.1.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/installed-package-contents": "^2.1.0",
@@ -20114,7 +20872,7 @@
                 },
                 "libnpmexec": {
                     "version": "8.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/run-script": "^8.1.0",
@@ -20130,14 +20888,14 @@
                 },
                 "libnpmfund": {
                     "version": "5.0.11",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3"
                     }
                 },
                 "libnpmhook": {
                     "version": "10.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20145,7 +20903,7 @@
                 },
                 "libnpmorg": {
                     "version": "6.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20153,7 +20911,7 @@
                 },
                 "libnpmpack": {
                     "version": "7.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/run-script": "^8.1.0",
@@ -20163,7 +20921,7 @@
                 },
                 "libnpmpublish": {
                     "version": "9.0.9",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ci-info": "^4.0.0",
                         "normalize-package-data": "^6.0.1",
@@ -20177,14 +20935,14 @@
                 },
                 "libnpmsearch": {
                     "version": "7.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-registry-fetch": "^17.0.1"
                     }
                 },
                 "libnpmteam": {
                     "version": "6.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20192,7 +20950,7 @@
                 },
                 "libnpmversion": {
                     "version": "6.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.7",
                         "@npmcli/run-script": "^8.1.0",
@@ -20203,11 +20961,11 @@
                 },
                 "lru-cache": {
                     "version": "10.2.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "make-fetch-happen": {
                     "version": "13.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/agent": "^2.0.0",
                         "cacache": "^18.0.0",
@@ -20225,25 +20983,25 @@
                 },
                 "minimatch": {
                     "version": "9.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "brace-expansion": "^2.0.1"
                     }
                 },
                 "minipass": {
                     "version": "7.1.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "minipass-collect": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "minipass-fetch": {
                     "version": "3.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "encoding": "^0.1.13",
                         "minipass": "^7.0.3",
@@ -20253,14 +21011,14 @@
                 },
                 "minipass-flush": {
                     "version": "1.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20269,7 +21027,7 @@
                 },
                 "minipass-json-stream": {
                     "version": "1.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "jsonparse": "^1.3.1",
                         "minipass": "^3.0.0"
@@ -20277,7 +21035,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20286,14 +21044,14 @@
                 },
                 "minipass-pipeline": {
                     "version": "1.2.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20302,14 +21060,14 @@
                 },
                 "minipass-sized": {
                     "version": "1.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20318,7 +21076,7 @@
                 },
                 "minizlib": {
                     "version": "2.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0",
                         "yallist": "^4.0.0"
@@ -20326,7 +21084,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20335,23 +21093,23 @@
                 },
                 "mkdirp": {
                     "version": "1.0.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ms": {
                     "version": "2.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "mute-stream": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "negotiator": {
                     "version": "0.6.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "node-gyp": {
                     "version": "10.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "env-paths": "^2.2.0",
                         "exponential-backoff": "^3.1.1",
@@ -20367,20 +21125,20 @@
                     "dependencies": {
                         "proc-log": {
                             "version": "3.0.0",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "nopt": {
                     "version": "7.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "abbrev": "^2.0.0"
                     }
                 },
                 "normalize-package-data": {
                     "version": "6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hosted-git-info": "^7.0.0",
                         "is-core-module": "^2.8.1",
@@ -20390,29 +21148,29 @@
                 },
                 "npm-audit-report": {
                     "version": "5.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "npm-bundled": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-normalize-package-bin": "^3.0.0"
                     }
                 },
                 "npm-install-checks": {
                     "version": "6.3.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "semver": "^7.1.1"
                     }
                 },
                 "npm-normalize-package-bin": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "npm-package-arg": {
                     "version": "11.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hosted-git-info": "^7.0.0",
                         "proc-log": "^4.0.0",
@@ -20422,14 +21180,14 @@
                 },
                 "npm-packlist": {
                     "version": "8.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ignore-walk": "^6.0.4"
                     }
                 },
                 "npm-pick-manifest": {
                     "version": "9.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-install-checks": "^6.0.0",
                         "npm-normalize-package-bin": "^3.0.0",
@@ -20439,7 +21197,7 @@
                 },
                 "npm-profile": {
                     "version": "10.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-registry-fetch": "^17.0.1",
                         "proc-log": "^4.0.0"
@@ -20447,7 +21205,7 @@
                 },
                 "npm-registry-fetch": {
                     "version": "17.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/redact": "^2.0.0",
                         "make-fetch-happen": "^13.0.0",
@@ -20461,18 +21219,18 @@
                 },
                 "npm-user-validate": {
                     "version": "2.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "p-map": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aggregate-error": "^3.0.0"
                     }
                 },
                 "pacote": {
                     "version": "18.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.0",
                         "@npmcli/installed-package-contents": "^2.0.1",
@@ -20495,7 +21253,7 @@
                 },
                 "parse-conflict-json": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "json-parse-even-better-errors": "^3.0.0",
                         "just-diff": "^6.0.0",
@@ -20504,11 +21262,11 @@
                 },
                 "path-key": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "path-scurry": {
                     "version": "1.11.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "lru-cache": "^10.2.0",
                         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@@ -20516,7 +21274,7 @@
                 },
                 "postcss-selector-parser": {
                     "version": "6.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cssesc": "^3.0.0",
                         "util-deprecate": "^1.0.2"
@@ -20524,27 +21282,27 @@
                 },
                 "proc-log": {
                     "version": "4.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "proggy": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-all-reject-late": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-call-limit": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-inflight": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-retry": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "err-code": "^2.0.2",
                         "retry": "^0.12.0"
@@ -20552,29 +21310,29 @@
                 },
                 "promzard": {
                     "version": "1.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "read": "^3.0.1"
                     }
                 },
                 "qrcode-terminal": {
                     "version": "0.12.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "read": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "mute-stream": "^1.0.0"
                     }
                 },
                 "read-cmd-shim": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "read-package-json-fast": {
                     "version": "3.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "json-parse-even-better-errors": "^3.0.0",
                         "npm-normalize-package-bin": "^3.0.0"
@@ -20582,35 +21340,35 @@
                 },
                 "retry": {
                     "version": "0.12.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true
                 },
                 "semver": {
                     "version": "7.6.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "shebang-command": {
                     "version": "2.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "shebang-regex": "^3.0.0"
                     }
                 },
                 "shebang-regex": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "signal-exit": {
                     "version": "4.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "sigstore": {
                     "version": "2.3.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.0.0",
@@ -20622,11 +21380,11 @@
                 },
                 "smart-buffer": {
                     "version": "4.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "socks": {
                     "version": "2.8.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ip-address": "^9.0.5",
                         "smart-buffer": "^4.2.0"
@@ -20634,7 +21392,7 @@
                 },
                 "socks-proxy-agent": {
                     "version": "8.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.1",
                         "debug": "^4.3.4",
@@ -20643,7 +21401,7 @@
                 },
                 "spdx-correct": {
                     "version": "3.2.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-expression-parse": "^3.0.0",
                         "spdx-license-ids": "^3.0.0"
@@ -20651,7 +21409,7 @@
                     "dependencies": {
                         "spdx-expression-parse": {
                             "version": "3.0.1",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "spdx-exceptions": "^2.1.0",
                                 "spdx-license-ids": "^3.0.0"
@@ -20661,11 +21419,11 @@
                 },
                 "spdx-exceptions": {
                     "version": "2.5.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "spdx-expression-parse": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-exceptions": "^2.1.0",
                         "spdx-license-ids": "^3.0.0"
@@ -20673,22 +21431,22 @@
                 },
                 "spdx-license-ids": {
                     "version": "3.0.18",
-                    "bundled": true
+                    "resolved": false
                 },
                 "sprintf-js": {
                     "version": "1.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ssri": {
                     "version": "10.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "string-width": {
                     "version": "4.2.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "emoji-regex": "^8.0.0",
                         "is-fullwidth-code-point": "^3.0.0",
@@ -20697,7 +21455,7 @@
                 },
                 "string-width-cjs": {
                     "version": "npm:string-width-cjs@4.2.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "emoji-regex": "^8.0.0",
                         "is-fullwidth-code-point": "^3.0.0",
@@ -20706,25 +21464,25 @@
                 },
                 "strip-ansi": {
                     "version": "6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-regex": "^5.0.1"
                     }
                 },
                 "strip-ansi-cjs": {
                     "version": "npm:strip-ansi-cjs@6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-regex": "^5.0.1"
                     }
                 },
                 "supports-color": {
                     "version": "9.4.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tar": {
                     "version": "6.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "chownr": "^2.0.0",
                         "fs-minipass": "^2.0.0",
@@ -20736,14 +21494,14 @@
                     "dependencies": {
                         "fs-minipass": {
                             "version": "2.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
-                                    "bundled": true,
+                                    "resolved": false,
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -20752,25 +21510,25 @@
                         },
                         "minipass": {
                             "version": "5.0.0",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "text-table": {
                     "version": "0.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tiny-relative-date": {
                     "version": "1.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "treeverse": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tuf-js": {
                     "version": "2.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@tufjs/models": "2.0.1",
                         "debug": "^4.3.4",
@@ -20779,25 +21537,25 @@
                 },
                 "unique-filename": {
                     "version": "3.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "unique-slug": "^4.0.0"
                     }
                 },
                 "unique-slug": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "imurmurhash": "^0.1.4"
                     }
                 },
                 "util-deprecate": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "validate-npm-package-license": {
                     "version": "3.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-correct": "^3.0.0",
                         "spdx-expression-parse": "^3.0.0"
@@ -20805,7 +21563,7 @@
                     "dependencies": {
                         "spdx-expression-parse": {
                             "version": "3.0.1",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "spdx-exceptions": "^2.1.0",
                                 "spdx-license-ids": "^3.0.0"
@@ -20815,28 +21573,28 @@
                 },
                 "validate-npm-package-name": {
                     "version": "5.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "walk-up-path": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "which": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "isexe": "^3.1.1"
                     },
                     "dependencies": {
                         "isexe": {
                             "version": "3.1.1",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "wrap-ansi": {
                     "version": "8.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-styles": "^6.1.0",
                         "string-width": "^5.0.1",
@@ -20845,15 +21603,15 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "6.0.1",
-                            "bundled": true
+                            "resolved": false
                         },
                         "emoji-regex": {
                             "version": "9.2.2",
-                            "bundled": true
+                            "resolved": false
                         },
                         "string-width": {
                             "version": "5.1.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "eastasianwidth": "^0.2.0",
                                 "emoji-regex": "^9.2.2",
@@ -20862,7 +21620,7 @@
                         },
                         "strip-ansi": {
                             "version": "7.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "ansi-regex": "^6.0.1"
                             }
@@ -20871,7 +21629,7 @@
                 },
                 "wrap-ansi-cjs": {
                     "version": "npm:wrap-ansi-cjs@7.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-styles": "^4.0.0",
                         "string-width": "^4.1.0",
@@ -20880,7 +21638,7 @@
                     "dependencies": {
                         "ansi-styles": {
                             "version": "4.3.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "color-convert": "^2.0.1"
                             }
@@ -20889,7 +21647,7 @@
                 },
                 "write-file-atomic": {
                     "version": "5.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "imurmurhash": "^0.1.4",
                         "signal-exit": "^4.0.1"
@@ -20897,7 +21655,7 @@
                 },
                 "yallist": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 }
             }
         },
@@ -22024,6 +22782,11 @@
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
             "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
         },
+        "punycode.js": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+            "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="
+        },
         "q": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -22069,8 +22832,7 @@
         "queue-microtask": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-            "dev": true
+            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
         },
         "queue-tick": {
             "version": "1.0.1",
@@ -22442,6 +23204,14 @@
                 }
             }
         },
+        "react-latex-next": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz",
+            "integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==",
+            "requires": {
+                "katex": "^0.16.0"
+            }
+        },
         "react-loading": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
@@ -22561,6 +23331,16 @@
                 "@react-types/shared": "^3.23.0"
             }
         },
+        "react-textarea-autosize": {
+            "version": "8.5.3",
+            "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
+            "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==",
+            "requires": {
+                "@babel/runtime": "^7.20.13",
+                "use-composed-ref": "^1.3.0",
+                "use-latest": "^1.2.1"
+            }
+        },
         "react-themeable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
@@ -22803,6 +23583,20 @@
                 "jsesc": "~0.5.0"
             }
         },
+        "rehype-katex": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz",
+            "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/katex": "^0.16.0",
+                "hast-util-from-html-isomorphic": "^2.0.0",
+                "hast-util-to-text": "^4.0.0",
+                "katex": "^0.16.0",
+                "unist-util-visit-parents": "^6.0.0",
+                "vfile": "^6.0.0"
+            }
+        },
         "rehype-raw": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
@@ -22831,6 +23625,17 @@
                 "unified": "^11.0.0"
             }
         },
+        "remark-math": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
+            "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+            "requires": {
+                "@types/mdast": "^4.0.0",
+                "mdast-util-math": "^3.0.0",
+                "micromark-extension-math": "^3.0.0",
+                "unified": "^11.0.0"
+            }
+        },
         "remark-parse": {
             "version": "11.0.0",
             "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -23092,6 +23897,12 @@
             "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
             "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
         },
+        "resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true
+        },
         "resolve-protobuf-schema": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
@@ -23135,8 +23946,7 @@
         "reusify": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-            "dev": true
+            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
         },
         "reveal.js": {
             "version": "5.1.0",
@@ -23183,7 +23993,6 @@
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
             "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-            "dev": true,
             "requires": {
                 "queue-microtask": "^1.2.2"
             }
@@ -23210,6 +24019,14 @@
                 }
             }
         },
+        "sade": {
+            "version": "1.8.1",
+            "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+            "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+            "requires": {
+                "mri": "^1.1.0"
+            }
+        },
         "safe-array-concat": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
@@ -23689,6 +24506,11 @@
             "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
             "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
         },
+        "slash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
+        },
         "slice-ansi": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -23880,6 +24702,23 @@
                 }
             }
         },
+        "speech-rule-engine": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
+            "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
+            "requires": {
+                "commander": "9.2.0",
+                "wicked-good-xpath": "1.3.0",
+                "xmldom-sre": "0.1.31"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "9.2.0",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
+                    "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
+                }
+            }
+        },
         "splaytree": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz",
@@ -24620,6 +25459,16 @@
             "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
             "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg=="
         },
+        "ts-api-utils": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+            "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ=="
+        },
+        "ts-dedent": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+            "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="
+        },
         "ts-loader": {
             "version": "9.5.1",
             "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
@@ -25114,6 +25963,17 @@
             "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.3.tgz",
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
+        "typescript-eslint": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.13.0.tgz",
+            "integrity": "sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/eslint-plugin": "7.13.0",
+                "@typescript-eslint/parser": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0"
+            }
+        },
         "typescript-language-server": {
             "version": "4.3.3",
             "resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
@@ -25137,6 +25997,11 @@
             "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
             "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg=="
         },
+        "uc.micro": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+            "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
+        },
         "uid-safe": {
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -25242,6 +26107,15 @@
                 "set-value": "^2.0.1"
             }
         },
+        "unist-util-find-after": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+            "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+            "requires": {
+                "@types/unist": "^3.0.0",
+                "unist-util-is": "^6.0.0"
+            }
+        },
         "unist-util-is": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
@@ -25417,11 +26291,24 @@
             "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
             "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
         },
+        "use-composed-ref": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
+            "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ=="
+        },
         "use-isomorphic-layout-effect": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
             "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA=="
         },
+        "use-latest": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
+            "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
+            "requires": {
+                "use-isomorphic-layout-effect": "^1.1.1"
+            }
+        },
         "use-sync-external-store": {
             "version": "1.2.2",
             "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
@@ -25467,6 +26354,24 @@
             "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
             "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
         },
+        "uvu": {
+            "version": "0.5.6",
+            "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+            "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+            "requires": {
+                "dequal": "^2.0.0",
+                "diff": "^5.0.0",
+                "kleur": "^4.0.3",
+                "sade": "^1.7.3"
+            },
+            "dependencies": {
+                "diff": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+                    "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="
+                }
+            }
+        },
         "v8-compile-cache": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz",
@@ -25484,6 +26389,16 @@
             "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
             "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="
         },
+        "validate.io-array": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
+            "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg=="
+        },
+        "validate.io-function": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
+            "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="
+        },
         "validator": {
             "version": "13.12.0",
             "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
@@ -25643,6 +26558,11 @@
             "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
             "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
         },
+        "web-worker": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz",
+            "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
+        },
         "webidl-conversions": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -26005,6 +26925,11 @@
                 "has-tostringtag": "^1.0.2"
             }
         },
+        "wicked-good-xpath": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
+            "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
+        },
         "wide-align": {
             "version": "1.1.5",
             "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -26194,6 +27119,11 @@
             "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
             "dev": true
         },
+        "xmldom-sre": {
+            "version": "0.1.31",
+            "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
+            "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
+        },
         "xmlhttprequest-ssl": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f42c15c14..80e13875c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -676,6 +676,7 @@ pie title Minerals in my tap water
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 50,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
             { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
@@ -684,7 +685,7 @@ pie title Minerals in my tap water
             ]},
 
             { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-
+            
 
 
             
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7ad5cd88c..6c95bf4db 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -550,7 +550,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faSatellite,
                 fa.faStar,
                 fa.faCloud,
-                fa.faBolt
+                fa.faBolt,
+                fa.faLightbulb
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 60befebec..907a5a343 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -613,9 +613,11 @@ export class CollectionCardView extends CollectionSubView() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const isDesc = BoolCast(this.Document.cardSort_isDesc)
 
+        console.log(this.childDocsWithoutLinks.length + "length")
+
         if (isEmpty) {
             return (
-                <span className="no-card-span" style={{ width: `100%` }}>
+                <span className="no-card-span" style={{ width: ` ${this._props.PanelWidth()}px`, height: ` ${this._props.PanelHeight()}px` }}>
                     Sorry ! There are no cards in this group
                 </span>
             );
@@ -676,6 +678,10 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     render() {
+        const isEmpty = this.childDocsWithoutLinks.length === 0;
+        const transformValue = `scale(${1 / this.fitContentScale})`;
+        const heightValue = `${100 * this.fitContentScale}%`;
+    
         return (
             <div
                 onPointerMove={e => this.onPointerMove(e)}
@@ -688,9 +694,9 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        transform: ` scale(${1 / this.fitContentScale})`,
-                        height: `${100 * this.fitContentScale}%`,
-                        gridAutoRows: `${100/this.numRows}%`
+                        ...(!isEmpty && { transform: transformValue }),
+                        ...(!isEmpty && { height: heightValue }),
+                        gridAutoRows: `${100 / this.numRows}%`
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
@@ -698,4 +704,5 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
+    
 }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 2ecde0eee..6ea1751a9 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -42,6 +42,7 @@ import { CollectionViewType } from '../../documents/DocumentTypes';
 // import { InkTranscription } from '../InkTranscription';
 import { Docs } from '../../documents/Documents';
 import { CollectionSubView } from '../collections/CollectionSubView';
+import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -145,7 +146,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile' | 'toggle-chat', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -242,6 +243,14 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
                 doc.cardSort_isDesc = true;
             },
         }],
+        ['toggle-chat', {
+            checkResult: (doc: Doc) => GPTPopup.Instance.visible,
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                GPTPopup.Instance.setVisible(!GPTPopup.Instance.visible);
+                GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+
+            },
+        }],
         ['pile', {
             checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform,
             setDoc: (doc: Doc, dv: DocumentView) => {
@@ -269,7 +278,8 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
 
                 // if (doc.type == DocumentType.COL ) {
                     newCol && dv.ComponentView?.addDocument?.(newCol);
-                    dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
+                    // dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
+                    DocumentView.showDocument(newCol, { willZoomCentered: true })
 
                 // }
                 // doc as CollectionFreeFormView
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 042b1dea5..291f272ce 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -7,10 +7,13 @@ $highlightedText: #82e0ff;
 
 .summary-box {
     position: fixed;
-    bottom: 10px;
-    right: 10px;
+    top: 115px;
+    left: 75px;
     width: 250px;
+    height: 200px;
     min-height: 200px;
+    min-width: 180px;
+
     border-radius: 16px;
     padding: 16px;
     padding-bottom: 0;
@@ -21,6 +24,18 @@ $highlightedText: #82e0ff;
     background-color: #ffffff;
     box-shadow: 0 2px 5px #7474748d;
     color: $textgrey;
+    resize: both; /* Allows resizing */
+    overflow: auto;
+
+    .resize-handle {
+        width: 10px;
+        height: 10px;
+        background: #ccc;
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        cursor: se-resize;
+      }
 
     .summary-heading {
         display: flex;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 5a5e80dc4..71d437c3d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -354,6 +354,84 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
+    cardMenu = () => (
+        <div className="btns-wrapper-gpt">
+            <Button
+                tooltip="Have ChatGPT sort your cards for you!"
+                text="Sort Cards!"
+                onClick={this.generateSort}
+                color={StrCast(Doc.UserDoc().userVariantColor)}
+                type={Type.TERT}
+                style={{
+                    width: '90%',
+                    textAlign: 'center',
+                    color: '#ffffff',
+                    fontSize: '16px',
+                }}
+            />
+            <Button
+                tooltip="Test your knowledge with ChatGPT!"
+                text="Quiz Cards!"
+                onClick={this.generateSort}
+                color={StrCast(Doc.UserDoc().userVariantColor)}
+                type={Type.TERT}
+                style={{
+                    width: '90%',
+                    textAlign: 'center',
+                    color: '#ffffff',
+                    fontSize: '16px',
+                }}
+            />
+        </div>
+
+    )
+
+    cardActual = (opt: string) => {
+        const isSort = opt === 'sort'
+
+        if (opt === 'sort'){
+            return (
+                <>
+                            <div className="btns-wrapper-gpt">
+                                <input
+                                    className="searchBox-input"
+                                    defaultValue=""
+                                    autoComplete="off"
+                                    onChange={this.sortPromptChanged}
+                                    onKeyDown={e => {
+                                        if (e.key === 'Enter') {
+                                            this.generateSort();
+                                        }
+                                        e.stopPropagation();
+                                    }}
+                                    type="text"
+                                    placeholder= {`How do you want to sort your cards ?` }
+                                    id="search-input"
+                                    style={{ width: '100%' }}
+                                />
+                            </div>
+                            <div className="btns-wrapper-gpt">
+                                <Button
+                                    tooltip="Have ChatGPT sort your cards for you!"
+                                    text="Sort!"
+                                    onClick={this.generateSort}
+                                    color={StrCast(Doc.UserDoc().userVariantColor)}
+                                    type={Type.TERT}
+                                    style={{
+                                        width: '90%',
+                                        textAlign: 'center',
+                                        color: '#ffffff',
+                                        fontSize: '16px',
+                                    }}
+                                />
+                            </div>
+                        </>
+            )
+
+        }
+
+}
+
     sortBox = () => (
     <>
         <div>
@@ -575,12 +653,22 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     render() {
         return (
-
-            <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null}
+            <div
+                className="summary-box"
+                style={{ display: this.visible ? 'flex' : 'none' }}
+            >
+                {this.mode === GPTPopupMode.SUMMARY
+                    ? this.summaryBox()
+                    : this.mode === GPTPopupMode.DATA
+                    ? this.dataAnalysisBox()
+                    : this.mode === GPTPopupMode.IMAGE
+                    ? this.imageBox()
+                    : this.mode === GPTPopupMode.SORT
+                    ? this.sortBox()
+                    : null}
+                <div className="resize-handle" />
             </div>
-
-
         );
     }
+    
 }
-- 
cgit v1.2.3-70-g09d2


From abacdb311ee658b74f009106f4e7751fc216af4b Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 18 Jul 2024 11:24:12 -0400
Subject: boutta get silly

---
 src/client/util/CurrentUserUtils.ts                |  27 +-
 src/client/views/DocumentButtonBar.scss            |   4 +
 src/client/views/DocumentButtonBar.tsx             |  42 ++++
 src/client/views/FilterPanel.tsx                   |   2 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 .../views/collections/CollectionCardDeckView.tsx   |   2 +-
 src/client/views/global/globalScripts.ts           | 201 ++++++++-------
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  23 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 277 ++++++++++++---------
 10 files changed, 356 insertions(+), 226 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 80e13875c..380d16686 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -676,7 +676,7 @@ pie title Minerals in my tap water
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 50,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
             { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
@@ -684,7 +684,8 @@ pie title Minerals in my tap water
                 { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             ]},
 
-            { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            
             
 
 
@@ -711,12 +712,13 @@ pie title Minerals in my tap water
     //         { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
     //     ]
     // }
-    static cardGroupTools(): Button[] {
+    static tagGroupTools(): Button[] {
         return [
-            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
 
 
             // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
@@ -847,7 +849,16 @@ pie title Minerals in my tap water
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
             // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            
+            { title: "Filter", icon:"=",     toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, 
+            subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, 
+            // [
+            //     { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+            // ]
+        },
             { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 11614d627..374d6ecd3 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -153,6 +153,10 @@ $linkGap: 3px;
 
     &:hover {
         background-color: $black;
+        
+        .documentButtonBar-pinTypes {
+            display: flex;
+        }
     }
 }
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a75c7098c..3d6436cb5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -286,6 +286,42 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     @computed
     get keywordButton() {
         const targetDoc = this.view0?.Document;
+
+        const metaBtn = (name: string, icon: IconProp) => {
+            const tooltip = `Show ${name}`;
+            return (
+                <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
+                    <div className="documentButtonBar-pinIcon">
+                        <FontAwesomeIcon
+                            className="documentdecorations-icon"
+                            style={{ width: 20 }}
+                            key={icon.toString()}
+                            size="sm"
+                            icon={icon}
+                            // onPointerEnter={action(() => {
+                            //     this.subPin =
+                            //         (pinLayoutView ? 'Layout' : '') +
+                            //         (pinLayoutView && pinContentView ? ' &' : '') +
+                            //         (pinContentView ? ' Content View' : '') +
+                            //         (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
+                            // })}
+                            // onPointerLeave={action(() => {
+                            //     this.subPin = '';
+                            // })}
+                            onClick={e => {
+                                name === 'keywords' ? targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels) : 'hi'
+
+                               
+                            }}
+                        />
+                    </div>
+                </Tooltip>
+            );
+        };
+
+
+
+
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                 <div
@@ -294,6 +330,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                     onClick={() => {
                         targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels;
                     }}>
+
+                    <div className="documentButtonBar-pinTypes">
+                        {/* {pinBtn(true, false, 'window-maximize')}
+                        {pinBtn(false, true, 'address-card')}
+                        {pinBtn(true, true, 'id-card')} */}
+                    </div>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c97edd7f0..5db627d23 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -222,7 +222,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
                     <div style={{ width: '100%' }}>
-                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo]} />
+                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                     </div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0d42f4099..f552540b8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -90,7 +90,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
-    @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+    @observable  _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
     @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 024db82a4..58bb67120 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -58,7 +58,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     private _widthUndo?: UndoManager.Batch;
 
     // eslint-disable-next-line no-use-before-define
-    public static Instance: PropertiesView | undefined;
+    public static Instance: PropertiesView; 
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 907a5a343..410ddc355 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -516,7 +516,7 @@ export class CollectionCardView extends CollectionSubView() {
      */
     openChatPopup = async () => {
         GPTPopup.Instance.setVisible(true);
-        GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+        GPTPopup.Instance.setMode(GPTPopupMode.CARD);
         const sortDesc = await this.childPairStringList(); // Await the promise to get the string result
         GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
         GPTPopup.Instance.setSortDesc(sortDesc.join());
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 616633bc1..a48e3f9d9 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,10 +1,9 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
-import { Doc, DocListCast, NumListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { Gestures } from '../../../pen-gestures/GestureTypes';
@@ -42,7 +41,9 @@ import { CollectionViewType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-
+import { PropertiesView } from '../PropertiesView';
+import { MainView } from '../MainView';
+import { SnappingManager } from '../../util/SnappingManager';
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
     return DocumentView.Selected().length <= 0;
@@ -145,7 +146,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile' | 'toggle-chat', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -192,34 +193,34 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color',
         }],
-        ['heart', {
-            checkResult: (doc: Doc) => isAttrFiltered('heart'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
-
-            }
-        }],
-        ['star', {
-            checkResult: (doc: Doc) => isAttrFiltered('star'),
-
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
-            }
-        }],
-        ['bolt', {
-            checkResult: (doc: Doc) => isAttrFiltered('bolt'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
-
-            }
-        }],
-        ['cloud', {
-            checkResult: (doc: Doc) => isAttrFiltered('cloud'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
-
-            }
-        }],
+        // ['heart', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('heart'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
+
+        //     }
+        // }],
+        // ['star', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('star'),
+
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
+        //     }
+        // }],
+        // ['bolt', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('bolt'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
+
+        //     }
+        // }],
+        // ['cloud', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('cloud'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
+
+        //     }
+        // }],
         ['chat', {
             checkResult: (doc: Doc) => {
                 
@@ -275,20 +276,9 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
                     }
                 }
 
-                // if (doc.type == DocumentType.COL ) {
                     newCol && dv.ComponentView?.addDocument?.(newCol);
-                    // dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
                     DocumentView.showDocument(newCol, { willZoomCentered: true })
 
-                // }
-                // doc as CollectionFreeFormView
-                // doc as typeof CollectionSubView
-                
-                // doc._props
-
-                // CollectionFreeFormView.addDocumemnt
-        
-                // doc?.addDocument; //this._props.addDocument(newCol)
             },
         }],
     ]);
@@ -306,11 +296,40 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     return undefined;
 });
 
-ScriptingGlobals.add(function cardHasLabel(label: string) {
+
+ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
-    const labelNum = Number(label) - 1;
-    return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
-}, '');
+
+    function isAttrFiltered(attr: string) {
+        return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
+    }
+
+        if (checkResult) {
+            return value=== 'opts' ? PropertiesView.Instance.openFilters : isAttrFiltered(value)
+        }
+
+        if (value != 'opts'){
+            isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
+        }
+        else {
+            SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+            SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
+
+            PropertiesView.Instance.CloseAll()
+            PropertiesView.Instance.openFilters = true
+        }
+
+    
+        return undefined;
+    }, '');
+
+
+
+// ScriptingGlobals.add(function cardHasLabel(label: string) {
+//     const selected = DocumentView.SelectedDocs().lastElement();
+//     const labelNum = Number(label) - 1;
+//     return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
+// }, '');
 
 // ScriptingGlobals.add(function setCardSort(attr: "Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", value?: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
@@ -400,55 +419,53 @@ ScriptingGlobals.add(function cardHasLabel(label: string) {
 //     // return undefined;
 // });
 
-ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
-    // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    const selected = DocumentView.SelectedDocs().lastElement();
-
-    if (checkResult) {
-        // console.log(attr + "attricute")
-        // console.log(map.get(attr)?.checkResult(selected) + "check result")
-        console.log(StrCast(selected?.cardSort) + 'check');
-        const hi = StrCast(selected?.cardSort);
-        return StrCast(selected?.cardSortForDropDown) ?? 'Time';
-    }
-
-    function docFields(doc: Doc): void {
-        switch (value) {
-            case 'Custom 1':
-                doc.cardSort_customField = 'like';
-                break;
-            case 'Custom 2':
-                doc.cardSort_customField = 'star';
-                break;
-            case 'Custom 3':
-                doc.cardSort_customField = 'idea';
-                break;
-            case 'Chat GPT':
-                doc.cardSort = 'custom';
-                doc.cardSort_customField = 'chat';
-                break;
-            default:
-                break;
-        }
-
-        doc.cardSort_visibleSortGroups = new List<number>();
-    }
+// ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
+//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+//     const selected = DocumentView.SelectedDocs().lastElement();
+//     if (checkResult) {
+//         // console.log(attr + "attricute")
+//         // console.log(map.get(attr)?.checkResult(selected) + "check result")
+//         console.log(StrCast(selected?.cardSort) + 'check');
+//         const hi = StrCast(selected?.cardSort);
+//         return StrCast(selected?.cardSortForDropDown) ?? 'Time';
+//     }
+//     function docFields(doc: Doc): void {
+//         switch (value) {
+//             case 'Custom 1':
+//                 doc.cardSort_customField = 'like';
+//                 break;
+//             case 'Custom 2':
+//                 doc.cardSort_customField = 'star';
+//                 break;
+//             case 'Custom 3':
+//                 doc.cardSort_customField = 'idea';
+//                 break;
+//             case 'Chat GPT':
+//                 doc.cardSort = 'custom';
+//                 doc.cardSort_customField = 'chat';
+//                 break;
+//             default:
+//                 break;
+//         }
+
+//         doc.cardSort_visibleSortGroups = new List<number>();
+//     }
 
-    // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-    DocumentView.Selected().map(dv => {
-        dv.Document.cardSortForDropDown = value;
+//     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
+//     DocumentView.Selected().map(dv => {
+//         dv.Document.cardSortForDropDown = value;
 
-        if (value != 'Chat GPT') {
-            dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
-        }
-        docFields(dv.Document);
-    });
+//         if (value != 'Chat GPT') {
+//             dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
+//         }
+//         docFields(dv.Document);
+//     });
 
-    return undefined;
+//     return undefined;
 
-    // map.get(attr)?.setDoc?.();
-    // return undefined;
-});
+//     // map.get(attr)?.setDoc?.();
+//     // return undefined;
+// });
 
 // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 291f272ce..57973ef34 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -71,10 +71,12 @@ $highlightedText: #82e0ff;
     }
 
     .btns-wrapper-gpt {
-        height: 50px;
+        height: 100%;
         display: flex;
         justify-content: center;
         align-items: center;
+        flex-direction: column;
+
         transform: translateY(30px);
 
 
@@ -93,17 +95,20 @@ $highlightedText: #82e0ff;
         }
 
         
-    }
 
-    button {
-        font-size: 9px;
-        padding: 10px;
-        color: #ffffff;
-        width: 100%;
-        background-color: $button;
-        border-radius: 5px;
+        
     }
 
+    // button {
+    //     font-size: 9px;
+    //     padding: 10px;
+    //     color: #ffffff;
+    //     width: 100%;
+    //     background-color: $button;
+    //     border-radius: 5px;
+
+    // }
+
     .text-btn {
         &:hover {
             background-color: $button;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 71d437c3d..bbd5ea630 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -4,7 +4,7 @@ import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { CgClose } from 'react-icons/cg';
+import { CgClose, CgPathBack, CgArrowLeftO, CgCornerUpLeft } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
 import { ClientUtils } from '../../../../ClientUtils';
@@ -33,6 +33,10 @@ export enum GPTPopupMode {
 }
 
 
+
+
+
+
 interface GPTPopupProps {}
 
 @observer
@@ -166,6 +170,15 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.chatSortPrompt = e.target.value;
     });
 
+
+    @observable quizAnswer: string = ""
+
+    quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this.quizAnswer = e.target.value;
+    });
+
+
+
     @observable private regenerateCallback: (() => Promise<void>) | null = null;
 
     @action public setRegenerateCallback(callback: () => Promise<void>) {
@@ -359,81 +372,133 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             <Button
                 tooltip="Have ChatGPT sort your cards for you!"
                 text="Sort Cards!"
-                onClick={this.generateSort}
+                onClick={() => this.setMode(GPTPopupMode.SORT)}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
                 style={{
-                    width: '90%',
+                    width: '100%',
+                    height: '40%',
                     textAlign: 'center',
                     color: '#ffffff',
                     fontSize: '16px',
+                    marginBottom: '10px'
                 }}
             />
             <Button
                 tooltip="Test your knowledge with ChatGPT!"
                 text="Quiz Cards!"
-                onClick={this.generateSort}
+                onClick={() => this.setMode(GPTPopupMode.QUIZ)}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
                 style={{
-                    width: '90%',
+                    width: '100%',
                     textAlign: 'center',
                     color: '#ffffff',
                     fontSize: '16px',
+                    height: '40%',
+
                 }}
             />
         </div>
-
     )
+    
+    cardActual = (opt: GPTPopupMode) => {
+        if (opt === GPTPopupMode.SORT) {
+            return (
+                !this.sortDone ? (
+                    <>
+                        <div className="btns-wrapper-gpt">
+                            <input
+                                className="searchBox-input"
+                                defaultValue=""
+                                autoComplete="off"
+                                onChange={this.sortPromptChanged}
+                                onKeyDown={e => {
+                                    if (e.key === 'Enter') {
+                                        this.generateSort();
+                                    }
+                                    e.stopPropagation();
+                                }}
+                                type="text"
+                                placeholder="How do you want to sort your cards?"
+                                id="search-input"
+                                style={{ width: '100%' }}
+                            />
+                        {/* </div>
+                        <div className="btns-wrapper-gpt"> */}
+                            <Button
+                                tooltip="Have ChatGPT sort your cards for you!"
+                                text="Sort!"
+                                onClick={this.generateSort}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                                type={Type.TERT}
+                                style={{
+                                    width: '100%',
+                                    textAlign: 'center',
+                                    color: '#ffffff',
+                                    fontSize: '16px',
+                                    height: '40%',
 
-    cardActual = (opt: string) => {
-        const isSort = opt === 'sort'
-
-        if (opt === 'sort'){
+                                }}
+                            />
+                        </div>
+                    </>
+                ) : (
+                    <div>
+                        <div className="content-wrapper">
+                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
+                            <IconButton
+                                tooltip="Generate Again"
+                                onClick={() => this.setSortDone(false)}
+                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                            />
+                        </div>
+                    </div>
+                )
+            );
+        } else if (opt === GPTPopupMode.QUIZ) {
             return (
                 <>
-                            <div className="btns-wrapper-gpt">
-                                <input
-                                    className="searchBox-input"
-                                    defaultValue=""
-                                    autoComplete="off"
-                                    onChange={this.sortPromptChanged}
-                                    onKeyDown={e => {
-                                        if (e.key === 'Enter') {
-                                            this.generateSort();
-                                        }
-                                        e.stopPropagation();
-                                    }}
-                                    type="text"
-                                    placeholder= {`How do you want to sort your cards ?` }
-                                    id="search-input"
-                                    style={{ width: '100%' }}
-                                />
-                            </div>
-                            <div className="btns-wrapper-gpt">
-                                <Button
-                                    tooltip="Have ChatGPT sort your cards for you!"
-                                    text="Sort!"
-                                    onClick={this.generateSort}
-                                    color={StrCast(Doc.UserDoc().userVariantColor)}
-                                    type={Type.TERT}
-                                    style={{
-                                        width: '90%',
-                                        textAlign: 'center',
-                                        color: '#ffffff',
-                                        fontSize: '16px',
-                                    }}
-                                />
-                            </div>
-                        </>
-            )
-
+                    <div className="btns-wrapper-gpt">
+                        <input
+                            className="searchBox-input"
+                            defaultValue=""
+                            autoComplete="off"
+                            onChange={this.quizAnswerChanged}
+                            onKeyDown={e => {
+                                if (e.key === 'Enter') {
+                                    this.generateSort();
+                                }
+                                e.stopPropagation();
+                            }}
+                            type="text"
+                            placeholder="What is the selected card?"
+                            id="search-input"
+                            style={{ width: '100%' }}
+                        />
+                    </div>
+                    <div className="btns-wrapper-gpt">
+                        <Button
+                            tooltip="See how close you got to the right answer!"
+                            text="Check!"
+                            onClick={this.generateSort}
+                            color={StrCast(Doc.UserDoc().userVariantColor)}
+                            type={Type.TERT}
+                            style={{
+                                width: '90%',
+                                textAlign: 'center',
+                                color: '#ffffff',
+                                fontSize: '16px',
+                            }}
+                        />
+                    </div>
+                </>
+            );
         }
-
-}
-
+    };
+    
     sortBox = () => (
-    <>
         <div>
             {this.heading('SORTING')}
             <>
@@ -445,62 +510,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                         </div>
                     </div>
                 ) : (
-                    !this.sortDone && (
-                        <>
-                            <div className="btns-wrapper-gpt">
-                                <input
-                                    className="searchBox-input"
-                                    defaultValue=""
-                                    autoComplete="off"
-                                    onChange={this.sortPromptChanged}
-                                    onKeyDown={e => {
-                                        if (e.key === 'Enter') {
-                                            this.generateSort();
-                                        }
-                                        e.stopPropagation();
-                                    }}
-                                    type="text"
-                                    placeholder="How do you want to sort your cards ?"
-                                    id="search-input"
-                                    style={{ width: '100%' }}
-                                />
-                            </div>
-                            <div className="btns-wrapper-gpt">
-                                <Button
-                                    tooltip="Have ChatGPT sort your cards for you!"
-                                    text="Sort!"
-                                    onClick={this.generateSort}
-                                    color={StrCast(Doc.UserDoc().userVariantColor)}
-                                    type={Type.TERT}
-                                    style={{
-                                        width: '90%',
-                                        textAlign: 'center',
-                                        color: '#ffffff',
-                                        fontSize: '16px',
-                                    }}
-                                />
-                            </div>
-                        </>
-                    )
-                )}
-
-                {this.sortDone && (
-                    <div>
-                        <div className="content-wrapper">
-                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
-                            <IconButton
-                                tooltip="Generate Again"
-                                onClick={() => this.setSortDone(false)}
-                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
-                            />
-                        </div>
-                    </div>
+                    (this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(this.mode)) // Call the functions to render JSX
                 )}
             </>
         </div>
-    </>
-);
+    );
+    
+    
 
     imageBox = () => (
         <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
@@ -647,25 +663,60 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     heading = (headingText: string) => (
         <div className="summary-heading">
             <label className="summary-text">{headingText}</label>
-            {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
+            {this.loading ? (
+                <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
+            ) : (
+                <>
+                {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && (
+                        <IconButton
+                            color={StrCast(SettingsManager.userVariantColor)}
+                            tooltip="back"
+                            icon={<CgCornerUpLeft size="16px" />}
+                            onClick={() => this.mode = GPTPopupMode.CARD}
+                            style = {{right: '-55px'}}
+                        />
+                    )}
+                    <IconButton
+                        color={StrCast(SettingsManager.userVariantColor)}
+                        tooltip="close"
+                        icon={<CgClose size="16px" />}
+                        onClick={() => this.setVisible(false)}
+                    />
+                    
+                </>
+            )}
         </div>
     );
+        
 
     render() {
+        let content;
+        
+        switch (this.mode) {
+            case GPTPopupMode.SUMMARY:
+                content = this.summaryBox();
+                break;
+            case GPTPopupMode.DATA:
+                content = this.dataAnalysisBox();
+                break;
+            case GPTPopupMode.IMAGE:
+                content = this.imageBox();
+                break;
+            case GPTPopupMode.SORT:
+            case GPTPopupMode.CARD:
+            case GPTPopupMode.QUIZ:
+                content = this.sortBox();
+                break;
+            default:
+                content = null;
+        }
+    
         return (
             <div
                 className="summary-box"
                 style={{ display: this.visible ? 'flex' : 'none' }}
             >
-                {this.mode === GPTPopupMode.SUMMARY
-                    ? this.summaryBox()
-                    : this.mode === GPTPopupMode.DATA
-                    ? this.dataAnalysisBox()
-                    : this.mode === GPTPopupMode.IMAGE
-                    ? this.imageBox()
-                    : this.mode === GPTPopupMode.SORT
-                    ? this.sortBox()
-                    : null}
+                {content}
                 <div className="resize-handle" />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From a882dc6899a6b11e2e972f3e2a2ae991caeea094 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 6 Aug 2024 14:48:49 -0400
Subject: locking into hotkey buttons

---
 src/client/util/CurrentUserUtils.ts      |  24 +--
 src/client/views/DocumentButtonBar.scss  |   5 +
 src/client/views/DocumentButtonBar.tsx   |  43 ++---
 src/client/views/DocumentDecorations.tsx |   9 +-
 src/client/views/FilterPanel.scss        |  64 +++++++
 src/client/views/FilterPanel.tsx         | 285 ++++++++++++++++++++++++++-----
 src/client/views/MainView.tsx            |   3 +-
 src/client/views/nodes/IconTagBox.tsx    |  38 +++--
 8 files changed, 380 insertions(+), 91 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 67f8dafc4..0d8955ced 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -44,6 +44,7 @@ export interface Button {
     title?: string;
     toolTip?: string;
     icon?: string;
+    isSystem?: boolean;
     btnType?: ButtonType;
     numBtnMin?: number;
     numBtnMax?: number;
@@ -713,14 +714,14 @@ pie title Minerals in my tap water
     //     ]
     // }
     static tagGroupTools(): Button[] {
-        return [
-            { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-
-
+        // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
+        return (Doc.UserDoc().activeDashboard ? [] : [
+            { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            
+            
             // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
@@ -728,7 +729,9 @@ pie title Minerals in my tap water
             // { title: "", icon, toolTip:"Click to toggle group 5's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-        ]
+        ]).concat([
+            { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
+        ])
     }
     static viewTools(): Button[] {
         return [
@@ -871,8 +874,9 @@ pie title Minerals in my tap water
     /// initializes a context menu button for the top bar context menu
     static setupContextMenuButton(params:Button, btnDoc?:Doc, btnContainer?:Doc) {
         const reqdOpts:DocumentOptions = {
+            isSystem: true,
             ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
-            color: Colors.WHITE, isSystem: true,
+            color: Colors.WHITE, 
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 374d6ecd3..ede277aae 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -29,6 +29,11 @@ $linkGap: 3px;
     background: black;
     height: 20px;
     align-items: center;
+
+    .tags {
+        width: 40px;
+
+    }
 }
 .documentButtonBar-followTypes {
     width: 20px;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c8df66535..c61ebfb4c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -288,7 +288,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         const targetDoc = this.view0?.Document;
 
         const metaBtn = (name: string, icon: IconProp) => {
-            const tooltip = `Show ${name}`;
+            const tooltip = `Toggle ${name}`;
             return (
                 <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
                     <div className="documentButtonBar-pinIcon">
@@ -298,18 +298,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            // onPointerEnter={action(() => {
-                            //     this.subPin =
-                            //         (pinLayoutView ? 'Layout' : '') +
-                            //         (pinLayoutView && pinContentView ? ' &' : '') +
-                            //         (pinContentView ? ' Content View' : '') +
-                            //         (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
-                            // })}
-                            // onPointerLeave={action(() => {
-                            //     this.subPin = '';
-                            // })}
                             onClick={e => {
-                                name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi'
+                                // console.log('wtfff')
+                                // name === 'tags' ?? 
+                                if (name === 'tags'){
+                                    (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags))
+                                } else {
+                                    (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels))
+                                }
+
+
 
                                
                             }}
@@ -323,24 +321,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
 
 
         return !targetDoc ? null : (
+            <div className='documentButtonBar-icon'>
+            <div className="documentButtonBar-pinTypes" style = {{width: '40px'}}>
+                {metaBtn('tags', 'star')}
+                {metaBtn("keywords", 'id-card')}
+             </div>
+
             <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
                     onClick={() => {
-                        targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels;
-                    }}>
+                        // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
+                    }}
+                    >
 
-                    <div className="documentButtonBar-pinTypes">
-                        {
-                            metaBtn('tags', "tag")
-                        /* {pinBtn(true, false, 'window-maximize')}
-                        {pinBtn(false, true, 'address-card')}
-                        {pinBtn(true, true, 'id-card')} */}
-                    </div>
+                   
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
+            </div>
+
         );
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b5d819b97..f3b4013f7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -703,6 +703,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
         const radiusHandle = (borderRadius / docMax) * maxDist;
         const radiusHandleLocation = Math.min(radiusHandle, maxDist);
+        
 
         const sharingMenu =
             Doc.IsSharingEnabled && docShareMode ? (
@@ -765,6 +766,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const centery = hideTitle ? 0 : this._titleHeight;
         const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
         const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v));
+      
+
+        const keyWordTrans = doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) : 0
+        const tagTrans = doc[DocData].showIconTags ? NumCast(doc[DocData].tagHeight) : 0
+
+
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
@@ -832,7 +839,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: `${(doc[DocData].showLabels || doc[DocData].showIconTags) ? 4 + (doc._keywordHeight as number) : 4}px`,
+                                        top: `${ keyWordTrans + tagTrans + 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index d6d2956aa..1877cc78b 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -1,3 +1,4 @@
+
 .filterBox-flyout {
     display: block;
     text-align: left;
@@ -228,6 +229,69 @@
     vertical-align: middle;
 }
 
+.filterHotKey-button {
+    pointer-events: auto;
+    // padding-right: 8px; //5px;
+    width: 100%; //width: 25px;
+    border-radius: 5px;
+    // margin-right: 20px;
+    margin-bottom: 8px;
+    border-color: #d3d3d3;
+    border-style: solid;
+    border-width: thin;
+    transition: all 0.3s ease-out;
+
+    &:hover{
+        border-color: #e9e9e9;
+        background-color: #878484
+    }
+
+
+
+    &.active {
+        background-color: #878484;
+
+
+
+        .icon-panel{
+            display: flex
+        }
+    }
+}
+
+.hotKeyButtons {
+    position: relative;
+    width: 100%;
+    //     margin-top: 3px;
+    //    // grid-column: 1/4;
+    //     width: 100%;
+    //     height: auto;
+    //     display: flex;
+    //     // flex-direction: row;
+    //     // flex-wrap: wrap;
+    //     padding-bottom: 5.5px;
+}
+
+.hotKey-icon-button {
+    // pointer-events: auto; /* Re-enable pointer events for the buttons */
+
+    //     width: 30px;
+    //     height: 30px;
+        // border-color: $medium-blue;
+        background-color: transparent;
+      &.active{
+
+
+      }
+
+}
+
+.icon-panel {
+    bottom: -14px;
+
+
+
+}
 // .sliderBox-outerDiv {
 //     width: 30%;// width: calc(100% - 14px);   // 14px accounts for handles that are at the max value of the slider that would extend outside the box
 //     height: 40; // height: 100%;
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 332a89a96..e5f9fdb51 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -28,6 +28,19 @@ import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DocumentOptions } from '../documents/Documents';
 import { DocUtils } from '../documents/DocUtils';
 import { dropActionType } from '../util/DropActionTypes';
+import { Toggle } from 'browndash-components';
+import { SettingsManager } from '../util/SettingsManager';
+import { StrCast } from '../../fields/Types';
+import { ToggleType } from 'browndash-components';
+import { MultiToggle } from 'browndash-components';
+import { Type } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { DocData } from '../../fields/DocSymbols';
+import { DocumentType } from '../documents/DocumentTypes';
+import { Tooltip } from '@mui/material';
+import { useLocalObservable } from 'mobx-react';
+import { useRef } from 'react';
+import { useEffect } from 'react';
 
 
 interface filterProps {
@@ -228,11 +241,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     };
 
     addHotkey() {
-
         const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-        const filter = buttons['Filter']
-        const filter2 = DocCast(filter)
-        const but2 = Doc.UserDoc().myContextMenuBtns
+        const filter = DocCast(buttons.Filter);
+        const filter2 = DocCast(filter);
+        const but2 = Doc.UserDoc().myContextMenuBtns;
+
+        console.log(DocCast(filter).data);
+
+        //[idToDoc("0bec0963-95ac-4978-8606-8af4c7b9badd"),idToDoc("f740e910-bf83-4ee3-b13a-1e63c49dd254"),idToDoc("61db82b0-025a-4cb9-937b-97b1290e8f1a"),idToDoc("1046859f-34ac-4421-bc07-ee2f2cf812e2"),idToDoc("badaff47-3c3a-4b78-a7d6-f2cbc8e45ba0")]
 
         // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
         //     _width: 250,
@@ -251,59 +267,135 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
         // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm)
 
-        const hi = CurrentUserUtils.contextMenuTools()
-
-        const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
-
-
-        const heyy = [...hi, newKey]
-
-        Doc.UserDoc().myFilterHotKeyIcons = "palette"
-        Doc.UserDoc().myFilterHotKeyTitles = "hi"
-
-        CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
-
-
-
-        Doc.UserDoc().hi = Docs.Create.FontIconDocument(
-           { title: '',
-                icon: 'map-pin',
-            
-                backgroundColor: '#ACCEF7',
-                layout_hideAllLinks: true,
-                _width: 15,
-                _height: 15,
-                _xPadding: 0,
-   }
-        )
-
-
-        // buttons['Filter']
+        //         const hi = CurrentUserUtils.contextMenuTools()
+
+        const newKey: Button = {
+            title: 'Bob',
+            icon: 'cloud',
+            toolTip: "Click to toggle the cloud group's visibility",
+            btnType: ButtonType.ToggleButton,
+            expertMode: false,
+            toolType: 'cloud',
+            funcs: {},
+            scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
+        };
+
+        //         const heyy = [...hi, newKey]
+
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['hi hi', 'supppp'])
+
+        Doc.UserDoc()['hi hi'] = 'heart'
+        Doc.UserDoc()['supppp'] = 'star'
+
+        //         CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
+
+        //         Doc.UserDoc().hi = Docs.Create.FontIconDocument(
+        //            { title: '',
+        //                 icon: 'map-pin',
+
+        //                 backgroundColor: '#ACCEF7',
+        //                 layout_hideAllLinks: true,
+        //                 _width: 15,
+        //                 _height: 15,
+        //                 _xPadding: 0,
+        //    }
+        //         )
+
+        //         // buttons['Filter']
+
+        // const reqdCtxtOpts: DocumentOptions = {
+        //     title: 'Filter',
+        //     undoIgnoreFields: new List<string>(['width', 'linearView_IsOpen']),
+        //     flexGap: 0,
+        //     childDragAction: dropActionType.embed,
+        //     childDontRegisterViews: true,
+        //     linearView_IsOpen: true,
+        //     ignoreClick: true,
+        //     linearView_Expandable: false,
+        //     _height: 35,
+        // };
+        const ctxtMenuBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
+        ctxtMenuBtn.isSystem = ctxtMenuBtn[DocData].isSystem = undefined;
+        Doc.AddDocToList(filter, 'data', ctxtMenuBtn);
+        // // // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
+
+        //  DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
 
+        // newCol && dv.ComponentView?.addDocument?.(newCol);
 
+        // console.log(but2 + "omgg")
 
+        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
 
-        const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
-        const ctxtMenuBtns =  CurrentUserUtils.setupContextMenuBtn(newKey, buttons);
-        // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
+        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
+    }
 
+    hotKeyButtons() {
+        const selected = DocumentView.SelectedDocs().lastElement();
 
-        DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
+        function isAttrFiltered(attr: string) {
+            if (selected && selected._childFilters != undefined && selected.type === DocumentType.COL) {
+                return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
+            } else {
+                return false;
+            }
+            // return selected._childFilters ? StrListCast(selected._childFilters).some(filter => filter.includes(attr)) : false;
+        }
 
+        const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        // newCol && dv.ComponentView?.addDocument?.(newCol);
+        console.log(hotKeys + 'hiihihi');
 
-        // console.log(but2 + "omgg")
+        const hi = ['star', 'heart', 'bolt'];
 
+        
 
 
-        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+        //selecting a button should make t so that the the icon on the top filter panel becomes said icon
 
+        const buttons = hotKeys.map((hotKey, i) => (
+            // <div key={i} className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
+            
+            // onClick={undoable((e: React.MouseEvent) => {
 
+            //     // if (e.target === e.currentTarget){
+            //         isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+            //     // }
+            // }, hotKey)}
 
-        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
+            // // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
+            
+            
+            // >
+                {/* <Toggle
+                    toggleStatus={isAttrFiltered(hotKey)}
+                    tooltip={'toggle filter'}
+                    text={hotKey}
+                    color={SettingsManager.userColor}
+                    icon={<FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any}/>}
+                    iconPlacement="left"
+                    align="flex-start"
+                    fillWidth
+                    toggleType={ToggleType.BUTTON}
+                    onClick={undoable(() => {
+                        isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+                    }, hotKey)}
+                /> */}
+                <>
+
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                        <HotKeyIconButton hotKey = {hotKey} />
+
+                    </Tooltip>
+
+                   </> 
+        ));
+
+        return buttons;
     }
 
+    // @observable iconPanelMap: Map<string, number> = new Map();
+
     render() {
         return (
             <div className="filterBox-treeView">
@@ -376,8 +468,9 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                             <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                         </div>
                     </div>
-
                 </div>
+
+                <div>{this.hotKeyButtons()}</div>
             </div>
         );
     }
@@ -486,3 +579,113 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return undefined;
     }
 }
+
+
+
+interface HotKeyButtonProps {
+    hotKey: string;
+    selected?: Doc
+}
+
+const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected}) => {
+    const state = useLocalObservable(() => ({
+        isActive: false,
+        toggleActive() {
+            this.isActive = !this.isActive;
+        },
+        deactivate() {
+            this.isActive = false;
+        }
+    }));
+
+    const panelRef = useRef<HTMLDivElement>(null);
+
+    const handleClick = () => {
+        state.toggleActive();
+
+        console.log(state.isActive + "hmmm")
+    };
+
+    const handleClickOutside = (event: MouseEvent) => {
+        if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
+            state.deactivate();
+        }
+    };
+
+    useEffect(() => {
+        document.addEventListener('mousedown', handleClickOutside);
+        return () => {
+            document.removeEventListener('mousedown', handleClickOutside);
+        };
+    }, []);
+
+    const iconOpts = ['star', 'heart', 'bolt'];
+
+    const iconPanel = iconOpts.map((icon, i) => (
+        <button key={i} onClick={() => Doc.UserDoc()[hotKey] = icon}>
+            <FontAwesomeIcon icon={icon as any} />
+        </button>
+    ));
+
+    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+
+    return (
+        <>
+
+        <div className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
+                    
+                    onClick={undoable((e: React.MouseEvent) => {
+
+                        // if (e.target === e.currentTarget){
+                            isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+                        // }
+                    }, hotKey)}
+
+                    // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
+                    
+                    
+                    >
+        <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
+            <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                <button
+                    type="button"
+                    onClick={(e: React.MouseEvent) => {
+                        console.log('hiii')
+                        e.stopPropagation(); 
+                        handleClick();
+
+                    }}
+
+                >
+                    <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl"/>
+                </button>
+            </Tooltip>
+            {state.isActive && (
+                <div className="icon-panel" >
+                    {iconPanel}
+                </div>
+            )}
+
+            
+        </div>
+
+        <p>     {hotKey.toUpperCase()}    </p>
+
+
+        <button onClick={(e: React.MouseEvent) => { 
+            e.stopPropagation(); 
+            Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
+        }}>
+            <FontAwesomeIcon icon={'x' as any} />
+        </button>
+
+        </div>
+
+
+        </>
+
+        
+    );
+});
+
+
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f552540b8..9da359e1b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -551,7 +551,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faStar,
                 fa.faCloud,
                 fa.faBolt,
-                fa.faLightbulb
+                fa.faLightbulb,
+                fa.faX
             ]
         );
     }
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 9a2273c3a..73360b684 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -41,24 +41,24 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         this.ref = React.createRef();
     }
 
-    componentDidMount(): void {
-        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-        this._props.doc._keywordHeight = this.height;
-
-        reaction(
-            () => this.currentScale,
-            () => {
-                if (this.currentScale < 1) {
-                    this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-                    this._props.doc._keywordHeight = this.height;
-                }
-            }
-        );
-    }
+    // componentDidMount(): void {
+    //     this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+    //     this._props.doc._keywordHeight = this.height;
+
+    //     reaction(
+    //         () => this.currentScale,
+    //         () => {
+    //             if (this.currentScale < 1) {
+    //                 this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+    //                 this._props.doc._keywordHeight = this.height;
+    //             }
+    //         }
+    //     );
+    // }
 
     componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
-        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-        this._props.doc._keywordHeight = this.height;
+        // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc[DocData].tagHeight = 36*this.currentScale;
     }
 
     /**
@@ -93,12 +93,16 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
             3: 'bolt'
         };
 
+
+
         return (
             <div
             className="card-button-container"
             style={{
                 transformOrigin: 'top left',
-                transform: `scale(${1 / this.currentScale}) translateY(${doc[DocData].showLabels ? doc._keywordHeight as number : 0}px)`,
+                transform: `scale(${1 / this.currentScale})
+                translateY(${doc[DocData].showLabels ? ((NumCast(doc[DocData].keywordHeight)*(1-this.currentScale))) : 0}px)
+                `,
                 width: `${totalWidth}px`,
                 fontSize: '50px'
             }}
-- 
cgit v1.2.3-70-g09d2


From 893a7cb517490ad5fc1f4737ff610779a57fdee7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 20:43:16 -0400
Subject: cleaning up card view stuff post-merge

---
 eslint.config.mjs                                  |  66 ++++++++++--
 src/ClientUtils.ts                                 |  34 ------
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionCardDeckView.tsx   |  78 ++++++--------
 src/client/views/global/globalScripts.ts           |  28 +++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   2 +-
 src/client/views/nodes/IconTagBox.tsx              | 120 +++++++++------------
 7 files changed, 153 insertions(+), 179 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/eslint.config.mjs b/eslint.config.mjs
index c69209327..619966f20 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,13 +1,57 @@
-import globals from "globals";
-import pluginJs from "@eslint/js";
-import tseslint from "typescript-eslint";
-import pluginReact from "eslint-plugin-react";
-
+import pluginJs from '@eslint/js';
+import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
 
 export default [
-  {files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]},
-  {languageOptions: { globals: globals.browser }},
-  pluginJs.configs.recommended,
-  ...tseslint.configs.recommended,
-  pluginReact.configs.flat.recommended,
-];
\ No newline at end of file
+    {
+        languageOptions: { globals: { ...globals.browser, ...globals.node } },
+    },
+    pluginJs.configs.recommended,
+    ...tseslint.configs.recommended,
+    {
+        rules: {
+            'node/no-missing-import': 0,
+            'no-console': 'off',
+            'func-names': 'off',
+            'no-process-exit': 'off',
+            'object-shorthand': 'off',
+            'class-methods-use-this': 'off',
+            'single-quote': 'off',
+            'max-classes-per-file': 0,
+
+            'react/jsx-filename-extension': [
+                2,
+                {
+                    extensions: ['.js', '.jsx', '.ts', '.tsx'],
+                },
+            ],
+
+            'import/prefer-default-export': 'off',
+            'no-unused-expressions': 'off',
+            '@typescript-eslint/no-unused-expressions': 'off',
+            'prefer-template': 'off',
+            'no-inner-declarations': 'off',
+            'no-plusplus': 'off',
+            'no-multi-assign': 'off',
+            'no-underscore-dangle': 'off',
+            'no-nested-ternary': 'off',
+            'lines-between-class-members': 'off',
+            'no-shadow': 'off',
+            '@typescript-eslint/no-shadow': 'warn',
+            'no-unused-vars': 'off',
+            '@typescript-eslint/no-unused-vars': 'error',
+            '@typescript-eslint/no-namespace': 'off',
+            'react/destructuring-assignment': 0,
+            'prefer-arrow-callback': 'error',
+            'no-return-assign': 'error',
+            'no-await-in-loop': 'error',
+            'no-loop-func': 'error',
+            'no-cond-assign': 'error',
+            'no-use-before-define': 'error',
+            'no-explicit-any': 'error',
+            'no-restricted-globals': ['error', 'event'],
+        },
+    },
+    pluginReactConfig,
+];
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 58648c5b6..01eda7e98 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -234,40 +234,6 @@ export namespace ClientUtils {
         return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
     }
 
-    export function hexToHsv(hex: string): [number, number, number] {
-        if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
-        const r = parseInt(hex.slice(1, 3), 16) / 255;
-        const g = parseInt(hex.slice(3, 5), 16) / 255;
-        const b = parseInt(hex.slice(5, 7), 16) / 255;
-        const max = Math.max(r, g, b),
-            min = Math.min(r, g, b);
-        const d = max - min;
-        let h: number;
-        const s = max === 0 ? 0 : d / max;
-        const v = max;
-    
-        switch (max) {
-            case min:
-                h = 0;
-                break;
-            case r:
-                h = (g - b) / d + (g < b ? 6 : 0);
-                break;
-            case g:
-                h = (b - r) / d + 2;
-                break;
-            case b:
-                h = (r - g) / d + 4;
-                break;
-            default:
-                h = 0;
-                break;
-        }
-        h /= 6;
-        return [h, s, v];
-    };
-    
-
     export function HSLtoRGB(h: number, s: number, l: number) {
         // Must be fractions of 1
         // s /= 100;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 74a788d94..8b8f85dfb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,7 +87,7 @@ export class MainView extends ObservableReactComponent<object> {
 
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
-    @observable  _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+    @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
     @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel;
@@ -549,7 +549,7 @@ export class MainView extends ObservableReactComponent<object> {
                 fa.faCloud,
                 fa.faBolt,
                 fa.faLightbulb,
-                fa.faX
+                fa.faX,
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index bb932b8b2..c029d17c6 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,7 +1,7 @@
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { ClientUtils, returnFalse, returnZero } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -18,13 +18,11 @@ import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { dropActionType } from '../../util/DropActionTypes';
 import { DocCast } from '../../../fields/Types';
 import { SelectionManager } from '../../util/SelectionManager';
 
-
-
 enum cardSortings {
     Time = 'time',
     Type = 'type',
@@ -36,9 +34,9 @@ enum cardSortings {
 }
 
 /**
- * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily 
+ * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily
  * sort and filter using presets, and customize your experience with chat gpt.
- * 
+ *
  * This file contains code as to how the docs are to be rendered (there place geographically and also in regards to sorting),
  * and callback functions for the gpt popup
  */
@@ -57,7 +55,6 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
-   
     static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
         try {
             const response = await fetch(imageUrl);
@@ -75,7 +72,7 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    constructor(props: any) {
+    constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
         this.setRegenerateCallback();
@@ -84,9 +81,7 @@ export class CollectionCardView extends CollectionSubView() {
     /**
      * Callback to ensure gpt's text versions of the child docs are updated
      */
-    setRegenerateCallback = () => {
-        GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
-    }
+    setRegenerateCallback = () => GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
 
     /**
      * update's gpt's doc-text list and initializes callbacks
@@ -99,7 +94,7 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.onQuizRandom = () => this.quizMode();
     };
 
-    componentDidMount(): void {
+    componentDidMount() {
         this.Document.childFilters_boolean = 'OR';
         this.childDocsWithoutLinks.forEach(c => {
             c[DocData].showIconTags = true;
@@ -112,7 +107,7 @@ export class CollectionCardView extends CollectionSubView() {
                 if (isVis) {
                     this.openChatPopup();
                 } else {
-                    this.Document.cardSort = (this.cardSort === cardSortings.Chat) ? '' : this.Document.cardSort;
+                    this.Document.cardSort = this.cardSort === cardSortings.Chat ? '' : this.Document.cardSort;
                 }
             }
         );
@@ -148,13 +143,13 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     /**
-     * When in quiz mode, randomly selects a document 
+     * When in quiz mode, randomly selects a document
      */
     quizMode = () => {
         const randomIndex = Math.floor(Math.random() * this.childDocs.length);
         SelectionManager.DeselectAll();
         DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false);
-    }
+    };
 
     /**
      * Number of rows of cards to be rendered
@@ -163,7 +158,6 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.ceil(this.sortedDocs.length / 10);
     }
 
-    
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
@@ -289,7 +283,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     /**
      * Resets all the doc dragging vairables once a card is dropped
-     * @param e 
+     * @param e
      * @param de drop event
      * @returns true if a card has been dropped, falls if not
      */
@@ -301,13 +295,12 @@ export class CollectionCardView extends CollectionSubView() {
             return true;
         }
         return false;
-    }, '' );
+    }, '');
 
-    get sortedDocs() {
+    @computed get sortedDocs() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
     }
 
-
     /**
      * Used to determine how to sort cards based on tags. The lestmost tags are given lower values while cards to the right are
      * given higher values. Decimals are used to determine placement for cards with multiple tags
@@ -345,7 +338,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         // Convert the result to a number and return
         return Number(numberString);
-    }
+    };
 
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
@@ -355,31 +348,27 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    @action sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
         docs.sort((docA, docB) => {
             const [typeA, typeB] = (() => {
                 switch (sortType) {
                     case cardSortings.Time:
                         return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
                     case cardSortings.Color:
-                        return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)), ClientUtils.hexToHsv(StrCast(docB.backgroundColor))];
+                        const d1 = DashColor(StrCast(docA.backgroundColor));
+                        const d2 = DashColor(StrCast(docB.backgroundColor));
+                        return [d1.hsv().hue(), d2.hsv().hue()];
                     case cardSortings.Tag:
                         return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
                     case cardSortings.Chat:
                         return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
-
                     default:
                         return [StrCast(docA.type), StrCast(docB.type)];
                 }
             })();
 
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-
-            if (isDesc) {
-                return out;
-            }
-
-            return -out;
+            return isDesc ? out : -out;
         });
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
@@ -389,12 +378,11 @@ export class CollectionCardView extends CollectionSubView() {
             docs.splice(dragIndex, 0, draggedDoc);
         }
 
-        return docs;
+        return [...docs]; // need to spread docs into a new object list since sort() modifies the incoming list which confuses mobx caching
     };
 
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
-            // eslint-disable-next-line react/jsx-props-no-spreading
             {...this._props}
             ref={action((r: DocumentView) => r?.ContentDiv && this._docRefs.set(doc, r))}
             Document={doc}
@@ -514,12 +502,11 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     /**
-     * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to 
+     * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to
      * usable code
      * @param gptOutput
      */
-    @action 
-    
+    @action
     processGptOutput = undoable((gptOutput: string, questionType: string, tag?: string) => {
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
@@ -530,11 +517,10 @@ export class CollectionCardView extends CollectionSubView() {
             });
         }
 
-        if (questionType ==='6'){
-            this.Document.cardSort = 'chat'
+        if (questionType === '6') {
+            this.Document.cardSort = 'chat';
         }
 
-
         listItems.forEach((item, index) => {
             const normalizedItem = item.trim();
             // find the corresponding Doc in the textToDoc map
@@ -578,7 +564,6 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }, '');
 
-
     /**
      * Opens up the chat popup and starts the process for smart sorting.
      */
@@ -586,13 +571,13 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.CARD);
         GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
-        await this.childPairStringListAndUpdateSortDesc()
+        await this.childPairStringListAndUpdateSortDesc();
     };
 
     /**
      * Actually renders all the cards
      */
-    renderCards = () => {
+    renderCards = (sortedDocs: Doc[]) => {
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const isDesc = BoolCast(this.Document.cardSort_isDesc);
@@ -606,12 +591,11 @@ export class CollectionCardView extends CollectionSubView() {
         }
 
         // Map sorted documents to their rendered components
-        return this.sortedDocs.map((doc, index) => {
-            const realIndex = this.sortedDocs.indexOf(doc);
+        return sortedDocs.map((doc, index) => {
+            const realIndex = sortedDocs.indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
             const isSelected = DocumentView.SelectedDocs().includes(doc);
-            const isDragging = DragManager.docsBeingDragged.includes(doc);
 
             const childScreenToLocal = () => {
                 this._forceChildXf;
@@ -625,7 +609,7 @@ export class CollectionCardView extends CollectionSubView() {
             const translateIfSelected = () => {
                 const indexInRow = index % this._maxRowCount;
                 const rowIndex = Math.trunc(index / this._maxRowCount);
-                const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2;
+                const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
             return (
@@ -680,7 +664,7 @@ export class CollectionCardView extends CollectionSubView() {
                         gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.renderCards()}
+                    {this.renderCards(this.sortedDocs)}
                 </div>
             </div>
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 65bd524fe..99bd537b1 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -283,23 +283,21 @@ ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
         return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
     }
 
-        if (checkResult) {
-            return value=== 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value)
-        }
+    if (checkResult) {
+        return value === 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value);
+    }
 
-        if (value != 'opts'){
-            isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
-        }
-        else {
-            SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
-            SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
-            PropertiesView.Instance?.CloseAll()
-            PropertiesView.Instance.openFilters = true
-        }
+    if (value != 'opts') {
+        isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
+    } else {
+        SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
+        PropertiesView.Instance?.CloseAll();
+        PropertiesView.Instance.openFilters = true;
+    }
 
-    
-        return undefined;
-    }, '');
+    return undefined;
+}, '');
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: string | number, checkResult?: boolean) {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 0fe112634..7feb17df1 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -157,7 +157,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
 
         let noviceList: string[] = [];
         let text: string | undefined;
-        let getStyle: (val: string) => { [key: string]: string } = () => ({});
+        const getStyle: (val: string) => { [key: string]: string } = () => ({});
         let icon: IconProp = 'caret-down';
         const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
         if (isViewDropdown) {
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 370ce51d1..e6c0961ed 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,31 +1,30 @@
-import React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { ObservableReactComponent } from "../ObservableReactComponent";
-import { NumCast } from "../../../fields/Types";
-import { Doc } from "../../../fields/Doc";
-import { numberRange } from "../../../Utils";
-import { Tooltip } from "@mui/material";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { undoable } from "../../util/UndoManager";
-import { BoolCast } from "../../../fields/Types";
-import { DocCast } from "../../../fields/Types";
+import React from 'react';
+import { observer } from 'mobx-react';
+import { computed } from 'mobx';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { NumCast } from '../../../fields/Types';
+import { Doc } from '../../../fields/Doc';
+import { numberRange } from '../../../Utils';
+import { Tooltip } from '@mui/material';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { undoable } from '../../util/UndoManager';
+import { BoolCast } from '../../../fields/Types';
+import { DocCast } from '../../../fields/Types';
 import './IconTagBox.scss';
-import {DocData } from '../../../fields/DocSymbols';
-import { StrListCast } from "../../../fields/Doc";
-import { StrCast } from "../../../fields/Types";
-import { CollectionViewType } from "../../documents/DocumentTypes";
-import { SnappingManager } from "../../util/SnappingManager";
-import { MainView } from "../MainView";
-import { PropertiesView } from "../PropertiesView";
-
+import { DocData } from '../../../fields/DocSymbols';
+import { StrListCast } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { SnappingManager } from '../../util/SnappingManager';
+import { MainView } from '../MainView';
+import { PropertiesView } from '../PropertiesView';
 
 export interface IconTagProps {
     doc: Doc;
 }
 
 /**
- * Renders the icon tags that rest under the document. The icons rendered are determined by the values of 
+ * Renders the icon tags that rest under the document. The icons rendered are determined by the values of
  * each icon in the userdoc.
  */
 @observer
@@ -33,7 +32,6 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     @computed
     get currentScale() {
         return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
-        
     }
 
     constructor(props: any) {
@@ -41,10 +39,9 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     }
 
     componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
-        this._props.doc[DocData].tagHeight = 36*this.currentScale;
+        this._props.doc[DocData].tagHeight = 36 * this.currentScale;
     }
 
-
     /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
      * @param doc
@@ -52,36 +49,32 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * @returns
      */
     renderButtons = (doc: Doc): JSX.Element | null => {
-        const amButtons = (StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length) + 1
+        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
 
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
-        
-        const totalWidth = (amButtons -1) * 35 + (amButtons -1) * 2 * 5 + 6;
+        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        const iconMap =  (buttonID: number)  => {
-            return StrCast(Doc.UserDoc()[keys[buttonID]])
+        const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6;
 
+        const iconMap = (buttonID: number) => {
+            return StrCast(Doc.UserDoc()[keys[buttonID]]);
         };
 
-        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card
-
-
+        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card;
 
         return (
             <div
-            className="card-button-container"
-            style={{
-                transformOrigin: 'top left',
-                transform: `scale(${ isCard ? 2 : 0.6 / this.currentScale})
-                translateY(${doc[DocData].showLabels ? ((NumCast(doc[DocData].keywordHeight)*(1-this.currentScale))) : 0}px)
+                className="card-button-container"
+                style={{
+                    transformOrigin: 'top left',
+                    transform: `scale(${isCard ? 2 : 0.6 / this.currentScale})
+                translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px)
                 `,
-                width: `${totalWidth}px`,
-                fontSize: '50px'
-            }}
-            >           
-                {numberRange(amButtons-1).map(i => (
+                    width: `${totalWidth}px`,
+                    fontSize: '50px',
+                }}>
+                {numberRange(amButtons - 1).map(i => (
                     <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i)} group</div>}>
-                        <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap(i) )}>
+                        <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}>
                             {this.getButtonIcon(doc, iconMap(i))}
                         </button>
                     </Tooltip>
@@ -96,47 +89,36 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
 
     openHotKeyMenu = () => {
         SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
-        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
+        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
 
-        PropertiesView.Instance.CloseAll()
-        PropertiesView.Instance.openFilters = true
-    }
+        PropertiesView.Instance.CloseAll();
+        PropertiesView.Instance.openFilters = true;
+    };
 
-      /**
+    /**
      * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
      * @param childPairIndex
      * @param buttonID
      * @param doc
      */
-      toggleButton = undoable((doc: Doc, icon: string) => {
-        BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
-
+    toggleButton = undoable((doc: Doc, icon: string) => {
+        BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true);
     }, 'toggle card tag');
 
-
     /**
      * Determines whether or not the given icon is active depending on the doc's data
-     * @param doc 
-     * @param icon 
-     * @returns 
+     * @param doc
+     * @param icon
+     * @returns
      */
     getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
-        const isActive = doc[icon]
+        const isActive = doc[icon];
         const color = isActive ? '#4476f7' : '#323232';
 
-        return <FontAwesomeIcon icon={icon} style={{ color , height: '30px', width: '30px'}} />;
+        return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />;
     };
 
-    render (){
-        return (
-            <>
-                {this.renderButtons(this._props.doc)}
-
-            </>
-        )
-
+    render() {
+        return <>{this.renderButtons(this._props.doc)}</>;
     }
-
 }
-
-
-- 
cgit v1.2.3-70-g09d2

