From 2e99ef0512b8f1b5c089637643ff6addf5afd943 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 4 Mar 2024 16:07:52 -0500
Subject: cleaned up a number of things related to autoHeight and
 stacking/masonry/notetaking views

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 56008de8e..266a2442f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -23,7 +23,7 @@ import { RichTextField } from '../../../../fields/RichTextField';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -758,7 +758,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @action
     toggleSidebar = (preview: boolean = false) => {
         const defaultSidebar = 250;
-        const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
+        const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
         if (preview) this._showSidebar = true;
         else {
             this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
-- 
cgit v1.2.3-70-g09d2


From 11b6cb8344f26e5895bebc3dedef491675717e30 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Mar 2024 15:11:29 -0500
Subject: fixed text in templates to revert to template value when noTemplate
 is set. made dashFieldView read from datadoc.  added margins for
 multicolumn/row, enabled chromHidden for multicolumn/row

---
 src/client/views/PropertiesView.tsx                       |  3 ++-
 .../collectionMulticolumn/CollectionMulticolumnView.tsx   | 10 ++++++----
 src/client/views/nodes/formattedText/DashFieldView.tsx    | 15 +++++++--------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  5 +++--
 4 files changed, 18 insertions(+), 15 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e4e7bec32..cbd3ff358 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -135,7 +135,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return this.selectedDoc?.isGroup;
     }
     @computed get isStack() {
-        return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
+        return [CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
     }
 
     rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
@@ -1076,6 +1076,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             <div className="transform-editor">
                 {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
                 {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
+                {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))}
                 {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
                 {this.isInk ? this.controlPointsButton : null}
                 {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 85fe7c896..125dd2781 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -315,7 +315,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
                     <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
                         {this.getDisplayDoc(layout)}
-                        <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+                        {this.layoutDoc._chromeHidden ? null : (
+                            <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+                        )}
                         <WidthLabel layout={layout} collectionDoc={this.Document} />
                     </div>
                 </Tooltip>,
@@ -350,14 +352,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 }}>
                 {this.contents}
                 {!this._startIndex ? null : (
-                    <Tooltip title={'scroll back'}>
+                    <Tooltip title="scroll back">
                         <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
                             <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
                         </div>
                     </Tooltip>
                 )}
-                {this._startIndex > this.childLayoutPairs.length - 1 ? null : (
-                    <Tooltip title={'scroll forward'}>
+                {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
+                    <Tooltip title="scroll forward">
                         <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
                             <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
                         </div>
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index b49e7dcf0..5c4d850ad 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -4,24 +4,23 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import Select from 'react-select';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Cast } from '../../../../fields/Types';
 import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
+import { FilterPanel } from '../../FilterPanel';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
-import { FilterPanel } from '../../FilterPanel';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -29,7 +28,7 @@ export class DashFieldView {
     node: any;
     tbox: FormattedTextBox;
 
-    unclickable = () => !this.tbox._props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+    unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this.node = node;
         this.tbox = tbox;
@@ -108,12 +107,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         super(props);
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
-        this._textBoxDoc = this._props.tbox.Document;
+        this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
 
         if (this._props.docId) {
             DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
         } else {
-            this._dashDoc = this._props.tbox.Document;
+            this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
         }
     }
 
@@ -202,7 +201,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 style={{
                     width: this._props.width,
                     height: this._props.height,
-                    pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+                    pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
                 {this._props.hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 266a2442f..2d5239f06 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -374,9 +374,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
                         if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
-                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
-                            dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
+                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined;
                             textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
+                            this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
+                            dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
                             unchanged = false;
                         }
                     } else {
-- 
cgit v1.2.3-70-g09d2


From bd9170eaa21a2fbccd0912f50d40cefaf0bbe47f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Mar 2024 12:28:24 -0500
Subject: cleaned up some dropActionType strings.  fixed stackingview scrolling
 when always active.  fixed schemaView keys dropdown menu to be a popup. 
 cleaned up  display of key Finfo's

---
 src/client/documents/Documents.ts                  |  46 +++----
 src/client/views/DashboardView.tsx                 |   9 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 .../views/collections/CollectionStackingView.tsx   |  15 ++-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionSchema/CollectionSchemaView.scss     | 149 +++++++++++----------
 .../collectionSchema/CollectionSchemaView.tsx      |  68 ++++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |   4 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 11 files changed, 170 insertions(+), 142 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 031560886..c9b5cb915 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -70,14 +70,14 @@ class EmptyBox {
 }
 
 export enum FInfoFieldType {
-    string,
-    boolean,
-    number,
-    Doc,
-    enumeration,
-    date,
-    list,
-    rtf,
+    string = 'string',
+    boolean = 'boolean',
+    number = 'number',
+    Doc = 'Doc',
+    enumeration = 'enum',
+    date = 'date',
+    list = 'list',
+    rtf = 'rich text',
 }
 export class FInfo {
     description: string = '';
@@ -198,20 +198,20 @@ type DATEt = DateInfo | number;
 type DTYPEt = DTypeInfo | string;
 export class DocumentOptions {
     // coordinate and dimensions depending on view
-    x?: NUMt = new NumInfo('x coordinate of document in a freeform view', false);
-    y?: NUMt = new NumInfo('y coordinate of document in a freeform view', false);
+    x?: NUMt = new NumInfo('horizontal coordinate in freeform view', false);
+    y?: NUMt = new NumInfo('vertical coordinate in freeform view', false);
     z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]);
-    overlayX?: NUMt = new NumInfo('x coordinate of document in a overlay view', false);
-    overlayY?: NUMt = new NumInfo('y coordinate of document in a overlay view', false);
-    text?: RTFt = new RtfInfo('rich text of a text doc', true);
+    overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false);
+    overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', false);
+    text?: RTFt = new RtfInfo('plain or rich text', true);
     _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
-    latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
-    longitude?: NUMt = new NumInfo('longitude coordinate for map views', false);
+    latitude?: NUMt = new NumInfo('latitude coordinate', false);
+    longitude?: NUMt = new NumInfo('longitude coordinate', false);
     routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordinates
-    markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document');
-    markerColor?: STRt = new StrInfo('Defines the marker color for a pushpin document');
-    map?: STRt = new StrInfo('text location of map');
+    markerType?: STRt = new StrInfo('marker type for a pushpin document');
+    markerColor?: STRt = new StrInfo('marker color for a pushpin document');
+    map?: STRt = new StrInfo('map location name');
     map_type?: STRt = new StrInfo('type of map view', false);
     map_zoom?: NUMt = new NumInfo('zoom of a map view', false);
     map_pitch?: NUMt = new NumInfo('pitch of a map view', false);
@@ -221,11 +221,11 @@ export class DocumentOptions {
     date_range?: STRt = new StrInfo('date range for calendar', false);
 
     wikiData?: STRt = new StrInfo('WikiData ID related to map location');
-    description?: STRt = new StrInfo('A description of the document');
-    _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
-    _timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden', false);
-    _width?: NUMt = new NumInfo('displayed width of a document');
-    _height?: NUMt = new NumInfo('displayed height of document');
+    description?: STRt = new StrInfo('description of document');
+    _timecodeToShow?: NUMt = new NumInfo('media timecode when document should appear (e.g., when an annotation shows up as a video plays)', false);
+    _timecodeToHide?: NUMt = new NumInfo('media timecode when document should disappear', false);
+    _width?: NUMt = new NumInfo("width of document in container's coordinates");
+    _height?: NUMt = new NumInfo("height of document in container's coordiantes");
     data_nativeWidth?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)', false);
     data_nativeHeight?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)', false);
     linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 472d419fc..439d250de 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -28,6 +28,7 @@ import { Colors } from './global/globalEnums';
 import { MainViewModal } from './MainViewModal';
 import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { dropActionType } from '../util/DragManager';
 
 enum DashboardGroup {
     MyDashboards,
@@ -403,7 +404,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
             _layout_fitWidth: true,
             _gridGap: 5,
             _forceActive: true,
-            childDragAction: 'embed',
+            childDragAction: dropActionType.embed,
             treeView_TruncateTitleWidth: 150,
             ignoreClick: true,
             contextMenuIcons: new List<string>(['plus']),
@@ -411,7 +412,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
             _lockedPosition: true,
             layout_boxShadow: '0 0',
             childDontRegisterViews: true,
-            dropAction: 'same',
+            dropAction: dropActionType.same,
             isSystem: true,
             layout_explainer: 'All of the calendars that you have created will appear here.',
         };
@@ -450,7 +451,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
             _layout_fitWidth: true,
             _gridGap: 5,
             _forceActive: true,
-            childDragAction: 'embed',
+            childDragAction: dropActionType.embed,
             treeView_TruncateTitleWidth: 150,
             ignoreClick: true,
             layout_headerButton: myTrailsBtn,
@@ -459,7 +460,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
             _lockedPosition: true,
             layout_boxShadow: '0 0',
             childDontRegisterViews: true,
-            dropAction: 'same',
+            dropAction: dropActionType.same,
             isSystem: true,
             layout_explainer: 'All of the trails that you have created will appear here.',
         };
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5d9abb460..00195f7d7 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -105,7 +105,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates?.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
-        templateMenu.push(<OtherToggle key={'default'} name={'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
+        templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
         addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
         this._addedKeys &&
             Array.from(this._addedKeys)
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index dc391631a..ea1caf58f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -233,10 +233,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
-    createRef = (ele: HTMLDivElement | null) => {
-        this._masonryGridRef = ele;
-        this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
-    };
 
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     @computed get onChildDoubleClickHandler() {
@@ -679,6 +675,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))));
 
+    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+    _oldWheel: any;
     render() {
         TraceMobx();
         const editableViewProps = {
@@ -699,7 +697,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 <div className="collectionStackingMasonry-cont">
                     <div
                         className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
-                        ref={this.createRef}
+                        ref={ele => {
+                            this._masonryGridRef = ele;
+                            this.createDashEventsTarget(ele); //so the whole grid is the drop target?
+                            this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                            this._oldWheel = ele;
+                            // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+                            ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+                        }}
                         style={{
                             overflowY: this.isContentActive() ? 'auto' : 'hidden',
                             background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fdbd1cc90..59a695a3d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -219,12 +219,13 @@ export function CollectionSubView<X>(moreProps?: X) {
                 const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
                 const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
                 if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
-                if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
+                if ((!dropAction || dropAction === dropActionType.inPlace || dropAction === dropActionType.same || dropAction === dropActionType.move || someMoved) && docDragData.moveDocument) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     if (movedDocs.length) {
                         const canAdd =
-                            (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
+                            (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) &&
+                            (dropAction !== dropActionType.inPlace || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
                         const moved = docDragData.moveDocument(movedDocs, this.Document, canAdd ? this.addDocument : returnFalse);
                         added = canAdd || moved ? moved : undefined;
                     } else if (addedDocs.length) {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4f2d8b9c0..285a789e6 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -449,10 +449,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
         const addDoc = inside ? this.localAdd : parentAddDoc;
         const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd;
-        if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
-            const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
+        if (canAdd && (dropAction !== dropActionType.inPlace || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+            const move = (!dropAction || canEmbed || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && moveDocument;
             this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
-            const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
+            const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === dropActionType.proto ? addDoc(d) : false) : addDoc(d)) || added, false);
             this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
             return res;
         }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index fed4e89cf..ac0bd2378 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -38,91 +38,97 @@
                 background: yellow;
             }
         }
+    }
 
-        .schema-column-menu,
-        .schema-filter-menu {
-            background: $light-gray;
-            position: absolute;
-            min-width: 200px;
-            max-width: 400px;
-            display: flex;
-            flex-direction: column;
-            align-items: flex-start;
-            z-index: 1;
-
-            .schema-key-search-input {
-                width: calc(100% - 20px);
-                margin: 10px;
-            }
+    .schema-preview-divider {
+        height: 100%;
+        background: black;
+        cursor: ew-resize;
+    }
+}
 
-            .schema-search-result {
-                cursor: pointer;
-                padding: 5px 10px;
-                width: 100%;
+.schema-column-menu,
+.schema-filter-menu {
+    background: $light-gray;
+    position: relative;
+    min-width: 200px;
+    max-width: 400px;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    z-index: 1;
 
-                &:hover {
-                    background-color: $medium-gray;
-                }
+    .schema-key-search-input {
+        width: calc(100% - 20px);
+        margin: 10px;
+    }
 
-                .schema-search-result-type,
-                .schema-search-result-desc {
-                    color: $dark-gray;
-                    font-size: $body-text;
-                }
-            }
+    .schema-search-result {
+        cursor: pointer;
+        padding: 5px 10px;
+        width: 100%;
 
-            .schema-key-search,
-            .schema-new-key-options {
-                width: 100%;
-                display: flex;
-                flex-direction: column;
-                align-items: flex-start;
-            }
+        &:hover {
+            background-color: $medium-gray;
+        }
+        .schema-search-result-type {
+            font-family: 'Courier New', Courier, monospace;
+        }
 
-            .schema-new-key-options {
-                margin: 10px;
-                .schema-key-warning {
-                    color: red;
-                    font-weight: normal;
-                    align-self: center;
-                }
-            }
+        .schema-search-result-type,
+        .schema-search-result-desc {
+            color: $dark-gray;
+            font-size: $body-text;
+        }
+        .schema-search-result-desc {
+            font-style: italic;
+        }
+    }
 
-            .schema-key-list {
-                width: 100%;
-                max-height: 300px;
-                overflow-y: auto;
-            }
+    .schema-key-search,
+    .schema-new-key-options {
+        width: 100%;
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+    }
 
-            .schema-key-type-option {
-                margin: 2px 0px;
+    .schema-new-key-options {
+        margin: 10px;
+        .schema-key-warning {
+            color: red;
+            font-weight: normal;
+            align-self: center;
+        }
+    }
 
-                input {
-                    margin-right: 5px;
-                }
-            }
+    .schema-key-list {
+        width: 100%;
+        max-height: 300px;
+        overflow-y: auto;
+    }
 
-            .schema-key-default-val {
-                margin: 5px 0;
-            }
+    .schema-key-type-option {
+        margin: 2px 0px;
 
-            .schema-column-menu-button {
-                cursor: pointer;
-                padding: 2px 5px;
-                background: $medium-blue;
-                border-radius: 9999px;
-                color: $white;
-                width: fit-content;
-                margin: 5px;
-                align-self: center;
-            }
+        input {
+            margin-right: 5px;
         }
     }
 
-    .schema-preview-divider {
-        height: 100%;
-        background: black;
-        cursor: ew-resize;
+    .schema-key-default-val {
+        margin: 5px 0;
+    }
+
+    .schema-column-menu-button {
+        cursor: pointer;
+        padding: 2px 5px;
+        background: $medium-blue;
+        border-radius: 9999px;
+        color: $white;
+        width: fit-content;
+        margin: 5px;
+        align-self: center;
     }
 }
 
@@ -133,6 +139,7 @@
     .row-menu {
         display: flex;
         justify-content: center;
+        cursor: pointer;
     }
 }
 
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index eee3836d2..12f0ad5e9 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, observe, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
@@ -25,6 +25,8 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+import { Popup, PopupTrigger, Type } from 'browndash-components';
+import { SettingsManager } from '../../../util/SettingsManager';
 const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export enum ColumnType {
@@ -161,7 +163,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                         (change as any).added.forEach((doc: Doc) => // for each document added 
                             Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
                                 Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
-                                    !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, key === 'author'))))));
+                                    !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
                         break;
                     case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
                 }
@@ -681,6 +683,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                         } else {
                             this.setKey(this._menuValue, this._newFieldDefault);
                         }
+                        this._columnMenuIndex = undefined;
                     })}>
                     done
                 </div>
@@ -688,12 +691,12 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 
-    onPassiveWheel = (e: WheelEvent) => {
+    onKeysPassiveWheel = (e: WheelEvent) => {
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
-        if (!this._oldWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
+        if (!this._oldKeysWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
         e.stopPropagation();
     };
-    _oldWheel: any;
+    _oldKeysWheel: any;
     @computed get keysDropdown() {
         return (
             <div className="schema-key-search">
@@ -708,9 +711,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                 <div
                     className="schema-key-list"
                     ref={r => {
-                        this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
-                        this._oldWheel = r;
-                        r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+                        this._oldKeysWheel?.removeEventListener('wheel', this.onKeysPassiveWheel);
+                        this._oldKeysWheel = r;
+                        r?.addEventListener('wheel', this.onKeysPassiveWheel, { passive: false });
                     }}>
                     {this._menuKeys.map(key => (
                         <div
@@ -721,14 +724,14 @@ export class CollectionSchemaView extends CollectionSubView() {
                             }}>
                             <p>
                                 <span className="schema-search-result-key">
-                                    {key}
-                                    {this.fieldInfos.get(key)!.fieldType ? ', ' : ''}
+                                    <b>{key}</b>
+                                    {this.fieldInfos.get(key)!.fieldType ? ':' : ''}
                                 </span>
                                 <span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}>
                                     {this.fieldInfos.get(key)!.fieldType}
                                 </span>
+                                <span className="schema-search-result-desc">&nbsp;&nbsp;{this.fieldInfos.get(key)!.description}</span>
                             </p>
-                            <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p>
                         </div>
                     ))}
                 </div>
@@ -740,16 +743,17 @@ export class CollectionSchemaView extends CollectionSubView() {
         const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
         return (
             <div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
-                <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+                <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+                {this._makeNewField ? this.newFieldMenu : this.keysDropdown}
+            </div>
+        );
+    }
+    get renderKeysMenu() {
+        console.log('RNDERMENUT:' + this._columnMenuIndex);
+        return (
+            <div className="schema-column-menu" style={{ left: 0, minWidth: CollectionSchemaView._minColWidth }}>
+                <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
                 {this._makeNewField ? this.newFieldMenu : this.keysDropdown}
-                <div
-                    className="schema-column-menu-button"
-                    onPointerDown={action(e => {
-                        e.stopPropagation();
-                        this.closeColumnMenu();
-                    })}>
-                    cancel
-                </div>
             </div>
         );
     }
@@ -833,6 +837,8 @@ export class CollectionSchemaView extends CollectionSubView() {
     isContentActive = () => this._props.isSelected() || this._props.isContentActive();
     screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
     previewWidthFunc = () => this.previewWidth;
+    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+    _oldWheel: any;
     render() {
         return (
             <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
@@ -841,15 +847,23 @@ export class CollectionSchemaView extends CollectionSubView() {
                     className="schema-table"
                     style={{ width: `calc(100% - ${this.previewWidth}px)` }}
                     onWheel={e => this._props.isContentActive() && e.stopPropagation()}
-                    ref={r => {
-                        // prevent wheel events from passively propagating up through containers
-                        r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
+                    ref={ele => {
+                        // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+                        this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                        (this._oldWheel = ele)?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                     }}>
                     <div className="schema-header-row" style={{ height: this.rowHeightFunc() }}>
                         <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
-                            <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
-                                <FontAwesomeIcon icon="plus" />
-                            </div>
+                            <Popup
+                                placement="right"
+                                background={SettingsManager.userBackgroundColor}
+                                color={SettingsManager.userColor}
+                                toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+                                trigger={PopupTrigger.CLICK}
+                                type={Type.TERT}
+                                isOpen={this._columnMenuIndex !== -1 ? false : undefined}
+                                popup={this.renderKeysMenu}
+                            />
                         </div>
                         {this.columnKeys.map((key, index) => (
                             <SchemaColumnHeader
@@ -870,7 +884,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             />
                         ))}
                     </div>
-                    {this._columnMenuIndex !== undefined && this.renderColumnMenu}
+                    {this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
                     <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
                     {this.layoutDoc.chromeHidden ? null : (
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 00e1f04c5..0a4325d8c 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -5,7 +5,7 @@ import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -54,7 +54,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
             if (separation > 100) {
                 const dragData = new DragManager.DocumentDragData([this.Document]);
-                dragData.dropAction = 'embed';
+                dragData.dropAction = dropActionType.embed;
                 dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
                 return true;
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index f6d94ce05..e38a42b29 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -11,7 +11,7 @@ import { Upload } from '../../../../server/SharedMediaTypes';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { Presentation } from '../../../util/TrackMovements';
 import { undoBatch } from '../../../util/UndoManager';
@@ -251,7 +251,7 @@ ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: b
 
 ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
     if (DocCast(value.doc)) {
-        DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], 'embed'), value.e.clientX, value.e.clientY);
+        DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], dropActionType.embed), value.e.clientX, value.e.clientY);
         value.e.preventDefault();
         return true;
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2d5239f06..4a1a51558 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -30,7 +30,7 @@ import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { LinkManager } from '../../../util/LinkManager';
 import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -624,7 +624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         docId: draggedDoc[Id],
                         float: 'unset',
                     });
-                    if (!['embed', 'copy'].includes((dropAction ?? '') as any)) {
+                    if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
                         added = dragData.removeDocument?.(draggedDoc) ? true : false;
                     } else {
                         added = true;
-- 
cgit v1.2.3-70-g09d2


From 7164179264d057986bfd5f303c61248f1c189406 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 8 Mar 2024 16:17:02 -0500
Subject: cleaned up MakeTemplate api and currentUserUtils templates for clicks
 etc.

---
 src/client/documents/Documents.ts                  |  9 ++++
 src/client/util/CurrentUserUtils.ts                | 35 ++++++++--------
 src/client/util/DropConverter.ts                   | 48 ++++++++++++++--------
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 5 files changed, 60 insertions(+), 38 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b7d8222b9..e6969d1f3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1024,6 +1024,15 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List<Doc>(), options);
         }
 
+        /**
+         * Creates a Doc to edit a script and write the compiled script into the specified field.
+         * Typically, this would be used to create a template that can then be applied to some other Doc
+         * in order to customize a behavior, such as onClick.
+         * @param script
+         * @param options
+         * @param fieldKey the field that the compiled script is written into.
+         * @returns the Scripting Doc
+         */
         export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
         }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index cfba003fc..564a46514 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -105,9 +105,12 @@ export class CurrentUserUtils {
             { opts: { title: "onCheckedClick"},      script: "console.log(heading, checked, containingTreeView)"},
         ];
         const reqdClickList = reqdTempOpts.map(opts => {
+            const title = opts.opts.title?.toString();
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
-            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title?.toString());
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+            const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
+            const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
         });
 
         const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
@@ -118,13 +121,13 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
-            { noteType: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
-            { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
-            { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
+            { title: "Postit", backgroundColor: "yellow",    icon: "sticky-note"},
+            { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb" },
+            { title: "Topic",  backgroundColor: "lightblue", icon: "book-open" }];
         const reqdNoteList = reqdTempOpts.map(opts => {
-            const reqdOpts = {...opts, isSystem:true, title: "text", width:200, layout_autoHeight: true, layout_fitWidth: true};
-            const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
-            return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
+            const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
+            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+            return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
         });
 
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
@@ -149,16 +152,16 @@ export class CurrentUserUtils {
         const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
 
         const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
-            const iconFieldName = "icon" + (type ? "_" + type : "");
-            const curIcon = DocCast(templateIconsDoc[iconFieldName]);
+            const title = "icon" + (type ? "_" + type : "");
+            const curIcon = DocCast(templateIconsDoc[title]);
             let creator = labelBox;
             switch (opts.iconTemplate) {
                 case DocumentType.IMG : creator = imageBox; break;
                 case DocumentType.FONTICON: creator = fontBox; break;
             }
-            const allopts = {isSystem: true, onClickScriptDisable: "never",  ...opts};
+            const allopts = {isSystem: true, onClickScriptDisable: "never",  ...opts, title};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
-                      DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts, templateField), true, iconFieldName, templateField))),
+                      DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
                 {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
@@ -211,7 +214,7 @@ export class CurrentUserUtils {
         };
         const headerBtnHgt = 10;
         const headerTemplate = (opts:DocumentOptions) => {
-            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", 
+            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Untitled Header", 
                 layout:
                 "<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
                 `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
@@ -224,7 +227,7 @@ export class CurrentUserUtils {
             // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
             // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
             // "</div>";
-            MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
+            MakeTemplate(Doc.GetProto(header));
             return header; 
         }
         const slideView = (opts:DocumentOptions) => {
@@ -232,9 +235,9 @@ export class CurrentUserUtils {
                     [
                         Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200,  isSystem: true }),
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
-                    ], opts);
+                    ], {...opts, title: "Untitled Slide View"});
 
-            MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
+            MakeTemplate(Doc.GetProto(slide));
             return slide; 
         }
         const plotlyApi = () => {
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ba981145d..3df3e36c6 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -12,43 +12,55 @@ import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'
 import { DragManager } from './DragManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
-export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') {
-    if (templateField) doc[DocData].title = templateField; /// the title determines which field is being templated
-    doc.isTemplateDoc = makeTemplate(doc, first, rename);
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc.  The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+    doc.isTemplateDoc = makeTemplate(doc, true);
     return doc;
 }
-//
-// converts 'doc' into a template that can be used to render other documents.
-// the title of doc is used to determine which field is being templated, so
-// passing a value for 'rename' allows the doc to be given a meangingful name
-// after it has been converted to
-function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
+
+/**
+ * Recursively converts 'doc' into a template that can be used to render other documents.
+ *
+ * For recurive Docs in the template, their target fieldKey is defined by their title,
+ * not by whatever fieldKey they used in their layout.
+ * @param doc
+ * @param first whether this is the topmost root of the recursive template
+ * @returns whether a template was successfully created
+ */
+function makeTemplate(doc: Doc, first: boolean = true): boolean {
     const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     if (layoutDoc.layout instanceof Doc) {
-        // its already a template
-        return true;
+        return true; // its already a template
     }
     const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
     const fieldKey = layout.replace("fieldKey={'", '').replace(/'}$/, '');
     const docs = DocListCast(layoutDoc[fieldKey]);
-    let any = false;
+    let isTemplate = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || any;
+            isTemplate = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || isTemplate;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
-            any = makeTemplate(d, false) || any;
+            isTemplate = makeTemplate(d, false) || isTemplate;
         }
     });
     if (first && !docs.length) {
         // bcz: feels hacky : if the root level document has items, it's not a field template
-        any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || any;
+        isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate;
     } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
+            isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
         }
     }
-    rename && (doc.title = rename);
-    return any;
+    return isTemplate;
 }
 export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
     data?.draggedDocuments.map((doc, i) => {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 285a789e6..0c0e49411 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -631,7 +631,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             }
             return (
                 <div>
-                    {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
+                    {!docs?.length || this.treeView.outlineMode || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
                         <div className="treeView-sorting">
                             <IconButton
                                 color={sortings[sorting]?.color}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4a1a51558..54e3e7b44 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -940,9 +940,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 description: 'Make Default Layout',
                 event: () => {
                     if (!this.layoutDoc.isTemplateDoc) {
-                        const title = StrCast(this.Document.title);
-                        this.Document.title = 'text';
-                        MakeTemplate(this.Document, true, title);
+                        MakeTemplate(this.Document);
                     }
                     Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
                     Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
-- 
cgit v1.2.3-70-g09d2


From f1ed1cff137c06afc4d4db8a8778f7827404889b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 9 Mar 2024 19:05:30 -0500
Subject: fixed up default text that uses a template to process an initial
 carriage return properly.  fixed text with inherited templates to be able to
 show fields with a default dashField value from template that can be
 overidden on instance.

---
 src/client/documents/Documents.ts                  | 11 +++---
 src/client/views/MarqueeAnnotator.tsx              |  4 +--
 src/client/views/PreviewCursor.tsx                 |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 36 +++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  4 +--
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  4 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 40 ++++++++++------------
 10 files changed, 55 insertions(+), 52 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e6969d1f3..7a3b965fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -3,7 +3,7 @@ import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
-import { Initializing } from '../../fields/DocSymbols';
+import { DocData, Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField, PointData } from '../../fields/InkField';
@@ -1981,10 +1981,8 @@ export namespace DocUtils {
         }
     }
 
-    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, backgroundColor?: string) {
+    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
         const tbox = Docs.Create.TextDocument('', {
-            _xMargin: noMargins ? 0 : undefined,
-            _yMargin: noMargins ? 0 : undefined,
             annotationOn,
             backgroundColor,
             _width: width || 200,
@@ -1997,11 +1995,14 @@ export namespace DocUtils {
             _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
             title,
         });
+
         const template = Doc.UserDoc().defaultTextLayout;
         if (template instanceof Doc) {
+            // if a default text template is specified
             tbox._width = NumCast(template._width);
             tbox.layout_fieldKey = 'layout_' + StrCast(template.title);
-            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template;
+            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template; // set the text doc's layout to render with the text template
+            tbox[DocData].proto = template; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
         }
         return tbox;
     }
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a4303c3aa..f59042b04 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -88,7 +88,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
             annotationOn: this.props.Document,
-            text: this.props.selectionText(),
+            text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
@@ -198,7 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
 
             const targetCreator = (annotationOn: Doc | undefined) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 456b753b4..a94c18295 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -85,7 +85,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else {
                     FormattedTextBox.PasteOnLoad = e;
                     if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
-                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
+                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined)), 'paste');
                 }
             }
             //pasting in images
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0c0e49411..c6bbcb0a5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -884,7 +884,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.Opacity:         return this.treeView.outlineMode ? undefined : 1;
-            case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+            case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4eb946939..9500b918a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -30,7 +30,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { freeformScrollMode } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
@@ -1146,23 +1146,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     isContentActive = () => this._props.isContentActive();
 
-    @undoBatch
+    /**
+     * Create a new text note of the same style as the one being typed into.
+     * If the text doc is be part of a larger templated doc, the new Doc will be a copy of the templated Doc
+     *
+     * @param fieldProps render props for the text doc being typed into
+     * @param below whether to place the new text Doc below or to the right of the one being typed into.
+     * @returns whether the new text doc was created and added successfully
+     */
+    createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
+        const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+        const newDoc = Doc.MakeCopy(textDoc, true);
+        newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+        newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+        newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+        FormattedTextBox.SetSelectOnLoad(newDoc);
+        FormattedTextBox.DontSelectInitialText = true;
+        return this.addDocument?.(newDoc);
+    }, 'copied text note');
+
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            const below = !e.altKey && e.key !== 'Tab';
-            const layout_fieldKey = StrCast(fieldProps.fieldKey);
-            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
-            const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
-            newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
-            else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
-            if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
-            }
-            newDoc[DocData].text = undefined;
-            FormattedTextBox.SetSelectOnLoad(newDoc);
-            return this.addDocument?.(newDoc);
+            return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
         }
     };
     @computed get childPointerEvents() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e22b83307..b913e05ad 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -178,7 +178,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
             FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
             FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
-            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
+            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 46867665d..ed1b519b4 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -134,7 +134,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                         if (shiftDown && enterKey) {
                             this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                         }
-                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
                         this._props.finishEdit?.();
                         return ret;
                     }, 'edit schema cell')}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index c185c66fc..927e6fad4 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -379,7 +379,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
@@ -592,7 +592,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+            text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
             config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 8a5bd7ce6..3eb051dbf 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -232,7 +232,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
@@ -466,7 +466,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
+            text: (StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location') as any,
             config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 54e3e7b44..1ff7274f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,7 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-import Select from 'react-select';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -100,7 +99,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _recordingStart: number = 0;
     private _ignoreScroll = false;
-    private _lastText = '';
     private _hadDownFocus = false;
     private _focusSpeed: Opt<number>;
     private _keymap: any = undefined;
@@ -306,7 +304,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             e.preventDefault();
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
@@ -1457,39 +1455,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
 
         const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
-        if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
-            const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+        const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+        if (selectOnLoad) {
             FormattedTextBox.SelectOnLoad = undefined;
+            FormattedTextBox.SelectOnLoadChar = '';
+        }
+        if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             this._props.select(false);
             if (selLoadChar) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
                 const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
                 const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
-                const tr = this._editorView.state.tr
-                    .setStoredMarks(storedMarks)
-                    .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
-                    .setStoredMarks(storedMarks);
+                const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
+                const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1);
+                const tr = tr2.setStoredMarks(storedMarks);
+
                 this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
             } else if (curText && !FormattedTextBox.DontSelectInitialText) {
                 selectAll(this._editorView.state, this._editorView?.dispatch);
             }
         }
-        selectOnLoad && this._editorView!.focus();
+        if (selectOnLoad) {
+            FormattedTextBox.DontSelectInitialText = false;
+            this._editorView!.focus();
+        }
         if (this._props.isContentActive()) this.prepareForTyping();
-        if (this._editorView) {
-            const tr = this._editorView.state.tr;
-            const { from, to } = tr.selection;
-            // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
-            if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
-
-            if (FormattedTextBox.PasteOnLoad) {
-                const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
-                FormattedTextBox.PasteOnLoad = undefined;
-                pdfAnchorId && this.addPdfReference(pdfAnchorId);
-            }
+        if (this._editorView && FormattedTextBox.PasteOnLoad) {
+            const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+            FormattedTextBox.PasteOnLoad = undefined;
+            pdfAnchorId && this.addPdfReference(pdfAnchorId);
         }
-        FormattedTextBox.DontSelectInitialText = false;
     }
 
     // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-- 
cgit v1.2.3-70-g09d2


From c563aec906c5728a5563fefac6ab573a31375641 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Mar 2024 09:09:59 -0400
Subject: made text templates be both layout templates and prototypes of new
 text documents.  fixed onPaint funcs to be undoable.  fixed comparisonBox to
 render a text box if it's fieldKey has a richtext field - this makes
 flashcard templates much easier.  fixed right-click on hyperlinks to bring up
 menu.   fixed layout_centered to be settable on templates.  added enable
 flashcard property for text.

---
 src/client/documents/Documents.ts                  | 32 ++++++++++++----------
 src/client/util/CurrentUserUtils.ts                |  4 +--
 src/client/views/PropertiesButtons.tsx             | 21 ++++++++++++--
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           | 13 ++++++---
 src/client/views/nodes/DocumentView.tsx            | 27 ++++++++++++++----
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +--
 .../views/nodes/formattedText/RichTextMenu.tsx     |  4 +--
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 10 files changed, 75 insertions(+), 36 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7a3b965fe..a13edec77 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1982,27 +1982,29 @@ export namespace DocUtils {
     }
 
     export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
+        const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
         const tbox = Docs.Create.TextDocument('', {
             annotationOn,
             backgroundColor,
-            _width: width || 200,
-            _height: 35,
-            x: x,
-            y: y,
-            _layout_centered: BoolCast(Doc.UserDoc().layout_centered),
-            _layout_fitWidth: true,
-            _layout_autoHeight: true,
-            _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
+            x,
+            y,
             title,
+            ...(defaultTextTemplate
+                ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+                : {
+                      _width: width || 200,
+                      _height: 35,
+                      _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
+                      _layout_fitWidth: true,
+                      _layout_autoHeight: true,
+                      _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
+                  }),
         });
 
-        const template = Doc.UserDoc().defaultTextLayout;
-        if (template instanceof Doc) {
-            // if a default text template is specified
-            tbox._width = NumCast(template._width);
-            tbox.layout_fieldKey = 'layout_' + StrCast(template.title);
-            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template; // set the text doc's layout to render with the text template
-            tbox[DocData].proto = template; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
+        if (defaultTextTemplate) {
+            tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
+            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
+            tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
         }
         return tbox;
     }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index fc315fdbe..84a33500d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -276,7 +276,7 @@ export class CurrentUserUtils {
             slide[DocData].text  = rtfield;
             slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
             slide[DocData]._type_collection = CollectionViewType.Freeform;
-            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"});
+            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
             return slide; 
         }
         const mermaidsApi = () => {
@@ -330,7 +330,7 @@ pie title Minerals in my tap water
             slide[DocData].text  = rtfield;
             slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
             slide[DocData]._type_collection = CollectionViewType.Freeform;
-            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"});
+            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
             return slide; 
         }
         const apis = [plotlyApi(), mermaidsApi()]
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 3cb835e39..02f288a68 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -13,7 +13,8 @@ import { RxWidth } from 'react-icons/rx';
 import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
 import { TfiBarChart } from 'react-icons/tfi';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+import { DocData } from '../../fields/DocSymbols';
+import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, ScriptCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
@@ -27,6 +28,7 @@ import { InkingStroke } from './InkingStroke';
 import './PropertiesButtons.scss';
 import { Colors } from './global/globalEnums';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 
 @observer
 export class PropertiesButtons extends React.Component<{}, {}> {
@@ -174,6 +176,20 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         );
     }
 
+    @computed get flashcardButton() {
+        return this.propertyToggleBtn(
+            on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'),
+            'layout_textPainted',
+            on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
+            on => <MdTouchApp />,
+            (dv, doc) => {
+                const on = doc.onPaint ? true : false;
+                doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
+                doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
+            }
+        );
+    }
+
     @computed get fitContentButton() {
         return this.propertyToggleBtn(
             on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
@@ -481,7 +497,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     render() {
         const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
-        const isText = layoutField instanceof RichTextField;
+        const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox;
         const isInk = this.selectedDoc?.layout_isSvg;
         const isImage = layoutField instanceof ImageField;
         const isMap = this.selectedDoc?.type === DocumentType.MAP;
@@ -507,6 +523,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {/* {toggle(this.freezeThumb)} */}
                 {toggle(this.forceActiveButton)}
                 {toggle(this.verticalAlignButton, { display: !isText ? 'none' : '' })}
+                {toggle(this.flashcardButton, { display: !isText ? 'none' : '' })}
                 {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */}
                 {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0794efe4c..ab811858a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -61,7 +61,7 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
         value: undefined,
     };
     e.stopPropagation();
-    UndoManager.RunInBatch(() => doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView');
+    ScriptCast(doc?.onPaint)?.script.run(scriptProps);
 }
 
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ef8c045cc..2b57178f4 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -14,6 +14,8 @@ import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { RichTextField } from '../../../fields/RichTextField';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -172,12 +174,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const displayDoc = (which: string) => {
             const whichDoc = DocCast(this.dataDoc[which]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
-            return targetDoc ? (
+            // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
+            const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+            return targetDoc || layoutTemplateString ? (
                 <>
                     <DocumentView
                         {...this._props}
-                        Document={targetDoc}
-                        TemplateDataDocument={undefined}
+                        renderDepth={this.props.renderDepth + 1}
+                        LayoutTemplateString={layoutTemplateString}
+                        Document={layoutTemplateString ? this.Document : targetDoc}
                         containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
@@ -190,7 +195,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         hideLinkButton={true}
                         pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                     />
-                    {clearButton(which)}
+                    {layoutTemplateString ? null : clearButton(which)}
                 </> // placeholder image if doc is missing
             ) : (
                 <div className="placeholder">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 29266bd8e..40592c2cd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -457,11 +457,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
     setToggleDetail = undoable(
-        (defaultLayout: string, scriptFieldKey: 'onClick') =>
+        (scriptFieldKey: 'onClick') =>
             (this.Document[scriptFieldKey] = ScriptField.MakeScript(
                 `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
                     .replace('layout_', '')
-                    .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
+                    .replace(/^layout$/, 'detail')}")`,
                 { documentView: 'any' }
             )),
         'set toggle detail'
@@ -1201,7 +1201,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     };
     public noOnClick = () => this._docViewInternal?.noOnClick();
     public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
-    public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey);
+    public setToggleDetail = (scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(scriptFieldKey);
     public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
     public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
     public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
@@ -1279,7 +1279,22 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
     }, 'set custom view');
 
+    /**
+     * This switches between the current view of a Doc and a specified alternate layout view.
+     * The current view of the Doc is stored in the layout_default field so that it can be restored.
+     * If the current view of the Doc is already the specified alternate layout view, this will switch
+     * back to the original layout (stored in layout_default)
+     * @param detailLayoutKeySuffix the name of the alternate layout field key (NOTE: 'layout_' will be prepended to this string to get the actual field nam)
+     */
+    public toggleDetail = (detailLayoutKeySuffix: string) => {
+        const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', '');
+        if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout;
+        const defaultLayout = StrCast(this.Document.layout_default);
+        if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
+        else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
+    };
     public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+        const batch = UndoManager.StartBatch('switchView:' + view);
         runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
@@ -1292,6 +1307,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                 setTimeout(
                     action(() => {
                         this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
+                        batch.end();
                         finished?.();
                     }),
                     Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
@@ -1451,9 +1467,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
     LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
 });
 
-ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') {
-    if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
-    else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
+    dv.toggleDetail(detailLayoutKeySuffix);
 });
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 39a45693e..89a5ac0b8 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -76,7 +76,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
         value = eq ? value.substring(1) : value;
         const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
         value = dubEq ? value.substring(2) : value;
-        const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: true };
+        const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true };
         if (dubEq) options.typecheck = false;
         const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() });
         return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1ff7274f8..1bd230891 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1574,7 +1574,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
         if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this._props.isContentActive()) {
+        else if (this._props.isContentActive() && !e.button) {
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             let xpos = pcords?.pos || 0;
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -2074,7 +2074,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         onScroll={this.onScroll}
                         onDrop={this.ondrop}>
                         <div
-                            className={`formattedTextBox-inner${rounded} ${this.layoutDoc.layout_centered ? 'centered' : ''}`}
+                            className={`formattedTextBox-inner${rounded} ${this.layoutDoc._layout_centered ? 'centered' : ''}`}
                             ref={this.createDropTarget}
                             style={{
                                 padding: StrCast(this.layoutDoc._textBoxPadding),
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cd0cdaa74..dc2c06701 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -104,7 +104,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this._activeAlignment;
     }
     @computed get textVcenter() {
-        return BoolCast(this.layoutDoc?.layout_centered);
+        return BoolCast(this.layoutDoc?._layout_centered);
     }
     _disposer: IReactionDisposer | undefined;
     componentDidMount() {
@@ -450,7 +450,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     vcenterToggle = (view: EditorView, dispatch: any) => {
-        this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered);
+        this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
     };
     align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
         if (this.TextView?._props.rootSelected?.()) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9bd41f42c..d5c91fc09 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -78,7 +78,7 @@ export class RichTextRules {
                 this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
                 const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey
                 this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key
-                this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view
+                this.TextBox.DocumentView?.().setToggleDetail('onPaint'); // create the script to toggle between the painted and regular view
                 this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text
 
                 return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block);
-- 
cgit v1.2.3-70-g09d2


From 641220e9cd9626af182118f84f8f775d7638cc67 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Mar 2024 12:17:24 -0400
Subject: fixed links to text to update automatically when textbox css styles
 change.

---
 src/client/views/nodes/LinkBox.tsx                        | 4 +++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 src/client/views/nodes/trails/PresBox.tsx                 | 1 -
 src/fields/DocSymbols.ts                                  | 2 +-
 4 files changed, 6 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index decdbb240..6e4d0e92a 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Xarrow from 'react-xarrows';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocCss, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
@@ -83,6 +83,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             b.Document[b.LayoutFieldKey];
             a.Document.layout_scrollTop;
             b.Document.layout_scrollTop;
+            a.Document[DocCss];
+            b.Document[DocCss];
 
             const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
             const bxf = b.screenToViewTransform();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1bd230891..e80c869d3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -746,7 +746,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
-        this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView)
+        this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
     };
 
     @observable _showSidebar = false;
@@ -1189,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._cachedLinks = LinkManager.Links(this.Document);
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
-            () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }),
+            () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
             (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
         );
         this._disposers.highlights = reaction(
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0d4f9ec78..e34144fae 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -738,7 +738,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const targetDoc: Doc = this.targetDoc;
         const finished = () => {
             afterNav?.();
-            console.log('Finish Slide Nav: ' + targetDoc.title);
             targetDoc[Animation] = undefined;
         };
         const selViewCache = Array.from(this.selectedArray);
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index f8a57acd5..64d657e4f 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -28,4 +28,4 @@ export const Initializing = Symbol('DocInitializing');
 export const ForceServerWrite = Symbol('DocForceServerWrite');
 export const CachedUpdates = Symbol('DocCachedUpdates');
 
-export const DashVersion = 'v0.7.0';
+export const DashVersion = 'v0.8.0';
-- 
cgit v1.2.3-70-g09d2


From 409a72c8c2546851fc824877b27a71d101839e7e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Mar 2024 10:55:29 -0400
Subject: cleaned up some audio recording  and annotating code

---
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/StyleProvider.tsx                 | 11 ++-
 src/client/views/nodes/DocumentView.tsx            | 90 ++++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++---
 src/client/views/pdf/AnchorMenu.tsx                |  6 +-
 src/fields/Doc.ts                                  |  4 +-
 src/fields/DocSymbols.ts                           | 35 +++++----
 7 files changed, 81 insertions(+), 88 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index f59042b04..3c35b441b 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -184,7 +184,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         };
         AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
         AnchorMenu.Instance.OnAudio = unimplementedFunction;
-        AnchorMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true);
         AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
         AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ab811858a..1adb0d9e5 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -50,6 +50,11 @@ export enum StyleProp {
     Highlighting = 'highlighting', // border highlighting
 }
 
+export enum AudioAnnoState {
+    stopped = 'stopped',
+    playing = 'playing',
+}
+
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
 }
@@ -330,14 +335,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const audio = () => {
-                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
+                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
                 const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
                 if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
-                const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
+                const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
                         <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
-                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
+                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
                         </div>
                     </Tooltip>
                 );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40592c2cd..bbeacef88 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -38,7 +38,7 @@ import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { StyleProp } from '../StyleProvider';
+import { AudioAnnoState, StyleProp } from '../StyleProvider';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
@@ -1004,49 +1004,41 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
         let gumStream: any;
         let recorder: any;
-        navigator.mediaDevices
-            .getUserMedia({
-                audio: true,
-            })
-            .then(function (stream) {
-                let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
-                if (audioTextAnnos) audioTextAnnos.push('');
-                else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
-                DictationManager.Controls.listen({
-                    interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
-                    continuous: { indefinite: false },
-                }).then(results => {
-                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                        DictationManager.Controls.stop();
-                    }
-                    onEnd?.();
-                });
-
-                gumStream = stream;
-                recorder = new MediaRecorder(stream);
-                recorder.ondataavailable = async (e: any) => {
-                    const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
-                    if (!(result instanceof Error)) {
-                        const audioField = new AudioField(result.accessPaths.agnostic.client);
-                        const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
-                        if (audioAnnos === undefined) {
-                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
-                        } else {
-                            audioAnnos.push(audioField);
-                        }
-                    }
-                };
-                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
-                recorder.start();
-                const stopFunc = () => {
-                    recorder.stop();
-                    DictationManager.Controls.stop(false);
-                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
-                    gumStream.getAudioTracks()[0].stop();
-                };
-                if (onRecording) onRecording(stopFunc);
-                else setTimeout(stopFunc, 5000);
+        navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
+            let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+            if (audioTextAnnos) audioTextAnnos.push('');
+            else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+            DictationManager.Controls.listen({
+                interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+                continuous: { indefinite: false },
+            }).then(results => {
+                if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                    DictationManager.Controls.stop();
+                }
+                onEnd?.();
             });
+
+            gumStream = stream;
+            recorder = new MediaRecorder(stream);
+            recorder.ondataavailable = async (e: any) => {
+                const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+                if (!(result instanceof Error)) {
+                    const audioField = new AudioField(result.accessPaths.agnostic.client);
+                    const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+                    if (audioAnnos) audioAnnos.push(audioField);
+                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                }
+            };
+            recorder.start();
+            const stopFunc = () => {
+                recorder.stop();
+                DictationManager.Controls.stop(false);
+                dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                gumStream.getAudioTracks()[0].stop();
+            };
+            if (onRecording) onRecording(stopFunc);
+            else setTimeout(stopFunc, 5000);
+        });
     }
 }
 
@@ -1231,25 +1223,25 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     public playAnnotation = () => {
         const self = this;
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+        const audioAnnoState = this.dataDoc.audioAnnoState ?? AudioAnnoState.stopped;
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos?.lastElement();
         if (anno instanceof AudioField) {
             switch (audioAnnoState) {
-                case 'stopped':
+                case AudioAnnoState.stopped:
                     this.dataDoc[AudioPlay] = new Howl({
                         src: [anno.url.href],
                         format: ['mp3'],
                         autoplay: true,
                         loop: false,
                         volume: 0.5,
-                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+                        onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
                     });
-                    this.dataDoc.audioAnnoState = 'playing';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.playing;
                     break;
-                case 'playing':
+                case AudioAnnoState.playing:
                     this.dataDoc[AudioPlay]?.stop();
-                    this.dataDoc.audioAnnoState = 'stopped';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
                     break;
             }
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e80c869d3..fb709818c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -51,7 +51,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -271,29 +271,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     let stopFunc: any;
                     const targetData = target[DocData];
                     targetData.mediaState = media_state.Recording;
-                    targetData.audioAnnoState = 'recording';
                     DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
-                    let reactionDisposer = reaction(
+                    const reactionDisposer = reaction(
                         () => target.mediaState,
-                        action(dictation => {
+                        dictation => {
                             if (!dictation) {
-                                targetData.audioAnnoState = 'stopped';
                                 stopFunc();
                                 reactionDisposer();
                             }
-                        })
+                        }
                     );
                     target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
                 }
             });
         };
-        AnchorMenu.Instance.Highlight = undoable(
-            action((color: string, isLinkButton: boolean) => {
-                this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
-                return undefined;
-            }),
-            'highlght text'
-        );
+        AnchorMenu.Instance.Highlight = undoable((color: string) => {
+            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+            return undefined;
+        }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
         AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
         /**
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d0688c338..59f191af0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -46,7 +46,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+    public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
     public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
     public Delete: () => void = unimplementedFunction;
     public PinToPres: () => void = unimplementedFunction;
@@ -118,8 +118,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     @action
-    highlightClicked = (e: React.MouseEvent) => {
-        this.Highlight(this.highlightColor, false, undefined, true);
+    highlightClicked = () => {
+        this.Highlight(this.highlightColor);
         AnchorMenu.Instance.fadeOut(true);
     };
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ea5411740..921d7aa5d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -15,7 +15,7 @@ import { incrementTitleCopy, Utils } from '../Utils';
 import { DateField } from './DateField';
 import {
     AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
-    DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
+    DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
     Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
@@ -227,7 +227,6 @@ export class Doc extends RefField {
                     DocAcl,
                     DocCss,
                     DocData,
-                    DocFields,
                     DocLayout,
                     DocViews,
                     FieldKeys,
@@ -308,7 +307,6 @@ export class Doc extends RefField {
             DocServer.UpdateField(this[Id], serverOp);
         }
     };
-    public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
     public [Width] = () => NumCast(this[SelfProxy]._width);
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [TransitionTimer]: any = undefined;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 64d657e4f..837fcc90e 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,8 +1,26 @@
-export const DocUpdated = Symbol('DocUpdated');
+// Symbols for fundamental Doc operations such as: permissions, field and proxy access and server interactions
+export const AclPrivate = Symbol('DocAclOwnerOnly');
+export const AclReadonly = Symbol('DocAclReadOnly');
+export const AclAugment = Symbol('DocAclAugment');
+export const AclSelfEdit = Symbol('DocAclSelfEdit');
+export const AclEdit = Symbol('DocAclEdit');
+export const AclAdmin = Symbol('DocAclAdmin');
+export const DocAcl = Symbol('DocAcl');
+export const CachedUpdates = Symbol('DocCachedUpdates');
+export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
+export const ForceServerWrite = Symbol('DocForceServerWrite');
 export const Self = Symbol('DocSelf');
 export const SelfProxy = Symbol('DocSelfProxy');
 export const FieldKeys = Symbol('DocFieldKeys');
 export const FieldTuples = Symbol('DocFieldTuples');
+export const Initializing = Symbol('DocInitializing');
+
+// Symbols for core Dash document model including data docs, layout docs, and links
+export const DocData = Symbol('DocData');
+export const DocLayout = Symbol('DocLayout');
+export const DirectLinks = Symbol('DocDirectLinks');
+
+// Symbols for view related operations for Documents
 export const AudioPlay = Symbol('DocAudioPlay');
 export const Width = Symbol('DocWidth');
 export const Height = Symbol('DocHeight');
@@ -10,22 +28,7 @@ export const Animation = Symbol('DocAnimation');
 export const Highlight = Symbol('DocHighlight');
 export const DocViews = Symbol('DocViews');
 export const Brushed = Symbol('DocBrushed');
-export const DocData = Symbol('DocData');
-export const DocLayout = Symbol('DocLayout');
-export const DocFields = Symbol('DocFields');
 export const DocCss = Symbol('DocCss');
-export const DocAcl = Symbol('DocAcl');
 export const TransitionTimer = Symbol('DocTransitionTimer');
-export const DirectLinks = Symbol('DocDirectLinks');
-export const AclPrivate = Symbol('DocAclOwnerOnly');
-export const AclReadonly = Symbol('DocAclReadOnly');
-export const AclAugment = Symbol('DocAclAugment');
-export const AclSelfEdit = Symbol('DocAclSelfEdit');
-export const AclEdit = Symbol('DocAclEdit');
-export const AclAdmin = Symbol('DocAclAdmin');
-export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
-export const Initializing = Symbol('DocInitializing');
-export const ForceServerWrite = Symbol('DocForceServerWrite');
-export const CachedUpdates = Symbol('DocCachedUpdates');
 
 export const DashVersion = 'v0.8.0';
-- 
cgit v1.2.3-70-g09d2


From a974aa4e6573c8becf93f78610406747fec14c1c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 19 Mar 2024 17:08:46 -0400
Subject: cleaned up user templates to not get changed on reload.  made setting
 a template add it to the template tools list and as a tools button.   fixed
 linking to parts of a template.   fixed disappearing templates caused by
 stacking view set a field with an empty key.   updated field assignment
 syntax in trees, dash field views, and key value box to all use :,:=,=,=:=
 syntax.  added text elide button.  added @(title) syntax for hyperlinking. 
 made using a template both inherit from the template to get default values
 and use the template to render.    fixed submenu placement of context menu. 
 updated RTF markdown doc.

---
 src/client/util/CurrentUserUtils.ts                | 19 +++--
 src/client/util/DropConverter.ts                   | 45 +++++-----
 src/client/util/LinkManager.ts                     | 13 +--
 src/client/util/RTFMarkup.tsx                      | 30 +++----
 src/client/views/ContextMenuItem.tsx               |  2 +-
 src/client/views/TemplateMenu.scss                 |  1 +
 src/client/views/TemplateMenu.tsx                  |  7 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  | 10 +--
 .../CollectionStackingViewFieldColumn.tsx          |  6 +-
 src/client/views/collections/TreeView.tsx          | 14 +++-
 src/client/views/global/globalScripts.ts           | 33 +++-----
 src/client/views/nodes/ComparisonBox.tsx           | 71 +++++++++-------
 src/client/views/nodes/DocumentView.tsx            | 32 ++++++-
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 22 ++---
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/KeyValuePair.tsx            |  2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    | 57 +++++++++----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++----
 .../views/nodes/formattedText/RichTextMenu.tsx     | 13 +++
 .../views/nodes/formattedText/RichTextRules.ts     | 97 ++++++++++++----------
 src/client/views/nodes/formattedText/marks_rts.ts  |  5 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |  2 +
 src/fields/Doc.ts                                  | 42 +++++-----
 src/fields/util.ts                                 |  4 +
 25 files changed, 328 insertions(+), 236 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 84a33500d..bbee18707 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -73,7 +73,7 @@ export class CurrentUserUtils {
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
         const reqdFuncs = { /* hidden: "IsNoviceMode()"  */ };
-        return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
+        return DocUtils.AssignScripts(userDocTemplates ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
     }
 
     /// Initializes templates for editing click funcs of a document 
@@ -133,11 +133,19 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
         return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
     }
+    static setupUserTemplates(doc: Doc, field="template_user") {
+        const tempUsers = DocCast(doc[field]);
+        const reqdUserList = DocListCast(tempUsers?.data);
+
+        const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+        return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
+    }
 
     /// Initializes collection of templates for notes and click functions
     static setupDocTemplates(doc: Doc, field="myTemplates") {
         const templates = [
             CurrentUserUtils.setupNoteTemplates(doc),
+            CurrentUserUtils.setupUserTemplates(doc),
             CurrentUserUtils.setupClickEditorTemplates(doc)
         ];
         CurrentUserUtils.setupChildClickEditors(doc)
@@ -375,9 +383,9 @@ pie title Minerals in my tap water
             { toolTip: "Tap or drag to create a flashcard",       title: "Flashcard", icon: "id-card",  dragFactory: doc.emptyFlashcard as Doc,  clickFactory: DocCast(doc.emptyFlashcard)},
             { toolTip: "Tap or drag to create an equation",       title: "Math", icon: "calculator",    dragFactory: doc.emptyEquation as Doc,   clickFactory: DocCast(doc.emptyEquation)},
             { toolTip: "Tap or drag to create a mermaid node",    title: "Mermaids", icon: "rocket",    dragFactory: doc.emptyMermaids as Doc,   clickFactory: DocCast(doc.emptyMermaids)}, 
-            { toolTip: "Tap or drag to create a plotly node",     title: "Plotly", icon: "rocket",      dragFactory: doc.emptyPlotly as Doc,   clickFactory: DocCast(doc.emptyMermaids)}, 
+            { toolTip: "Tap or drag to create a plotly node",     title: "Plotly", icon: "rocket",      dragFactory: doc.emptyPlotly as Doc,     clickFactory: DocCast(doc.emptyMermaids)}, 
             { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}}, 
-            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "book",       dragFactory: doc.emptyNoteboard as Doc,  clickFactory: DocCast(doc.emptyNoteboard)},
+            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "book",         dragFactory: doc.emptyNoteboard as Doc,  clickFactory: DocCast(doc.emptyNoteboard)},
             { toolTip: "Tap or drag to create a collection",      title: "Col", icon: "folder",         dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
             { toolTip: "Tap or drag to create a webpage",         title: "Web", icon: "globe-asia",     dragFactory: doc.emptyWebpage as Doc,    clickFactory: DocCast(doc.emptyWebpage)},
             { toolTip: "Tap or drag to create a comparison box",  title: "Compare", icon: "columns",    dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
@@ -385,10 +393,10 @@ pie title Minerals in my tap water
             { toolTip: "Tap or drag to create a map",             title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc,        clickFactory: DocCast(doc.emptyMap)},
             { toolTip: "Tap or drag to create a screen grabber",  title: "Grab", icon: "photo-video",   dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc,     clickFactory: DocCast(doc.emptyWebCam),     openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
-            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "circle",        dragFactory: doc.emptyButton as Doc,     clickFactory: DocCast(doc.emptyButton)},
+            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "circle",      dragFactory: doc.emptyButton as Doc,     clickFactory: DocCast(doc.emptyButton)},
             { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,     clickFactory: DocCast(doc.emptyScript),                                             funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "chart-bar",  dragFactory: doc.emptyDataViz as Doc,    clickFactory: DocCast(doc.emptyDataViz)},
-            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "person-chalkboard",     dragFactory: doc.emptySlide as Doc,      clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc,      clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a view slide",      title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader),     openFactoryAsDelegate: true,            funcs: { hidden: "IsNoviceMode()"} },
             { toolTip: "Toggle a Calculator REPL",                title: "replviewer",  icon: "calculator",                                      clickFactory: '<ScriptingRepl />' as any,   openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
@@ -738,6 +746,7 @@ pie title Minerals in my tap water
                 { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
                 { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             ]},
+            { title: "Elide",    toolTip: "Elide selection",      btnType: ButtonType.ToggleButton, icon: "eye",         toolType:"elide",     ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
             { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
             { title: "NoLink",   toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 3df3e36c6..ed5749d06 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -28,6 +28,7 @@ export function MakeTemplate(doc: Doc) {
 }
 
 /**
+ *
  * Recursively converts 'doc' into a template that can be used to render other documents.
  *
  * For recurive Docs in the template, their target fieldKey is defined by their title,
@@ -75,32 +76,36 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 remProps.map(prop => (dbox[prop] = undefined));
             }
         } else if (!doc.onDragStart && !doc.isButtonBar) {
-            const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
-            if (layoutDoc.type !== DocumentType.FONTICON) {
-                !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
-            }
-            layoutDoc.isTemplateDoc = true;
-            dbox = Docs.Create.FontIconDocument({
-                _nativeWidth: 100,
-                _nativeHeight: 100,
-                _width: 100,
-                _height: 100,
-                backgroundColor: StrCast(doc.backgroundColor),
-                title: StrCast(layoutDoc.title),
-                btnType: ButtonType.ClickButton,
-                icon: 'bolt',
-                isSystem: false,
-            });
-            dbox.title = ComputedField.MakeFunction('this.dragFactory.title');
-            dbox.dragFactory = layoutDoc;
-            dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
-            dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
+            dbox = makeUserTemplateButton(doc);
         } else if (doc.isButtonBar) {
             dbox.ignoreClick = true;
         }
         data.droppedDocuments[i] = dbox;
     });
 }
+export function makeUserTemplateButton(doc: Doc) {
+    const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+    if (layoutDoc.type !== DocumentType.FONTICON) {
+        !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
+    }
+    layoutDoc.isTemplateDoc = true;
+    const dbox = Docs.Create.FontIconDocument({
+        _nativeWidth: 100,
+        _nativeHeight: 100,
+        _width: 100,
+        _height: 100,
+        backgroundColor: StrCast(doc.backgroundColor),
+        title: StrCast(layoutDoc.title),
+        btnType: ButtonType.ClickButton,
+        icon: 'bolt',
+        isSystem: false,
+    });
+    dbox.title = ComputedField.MakeFunction('this.dragFactory.title');
+    dbox.dragFactory = layoutDoc;
+    dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
+    dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
+    return dbox;
+}
 ScriptingGlobals.add(
     function convertToButtons(dragData: any) {
         convertDropDataToButtons(dragData as DragManager.DocumentDragData);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 0c8d18a7a..cf16c4d6d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -58,8 +58,8 @@ export class LinkManager {
                                 link &&
                                     action(lAnchProtoProtos => {
                                         Doc.AddDocToList(Doc.UserDoc(), 'links', link);
-                                        lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link);
-                                        lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link);
+                                        lAnchs[0]?.[DocData][DirectLinks].add(link);
+                                        lAnchs[1]?.[DocData][DirectLinks].add(link);
                                     })
                             )
                         )
@@ -170,10 +170,11 @@ export class LinkManager {
             console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
             return [];
         }
-        const dirLinks = Doc.GetProto(anchor)[DirectLinks];
-        const annos = DocListCast(anchor[Doc.LayoutFieldKey(anchor) + '_annotations']);
-        if (!annos) debugger;
-        return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice());
+
+        const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as any));
+        const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
+        const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']);
+        return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks));
     }, true);
 
     // returns map of group type to anchor's links in that group type
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index f96d8a5df..315daad42 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -3,18 +3,12 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
 import { SettingsManager } from './SettingsManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
     static Instance: RTFMarkup;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
-    // private get linkVisible() {
-    //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
-    // }
-
     @action
     public open = () => (this.isOpen = true);
 
@@ -39,6 +33,10 @@ export class RTFMarkup extends React.Component<{}> {
                     <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
+                <p>
+                    <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+                    {` submit text to Chat GPT to have results appended afterward`}
+                </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
                     {`  add hashtag metadata to document. e.g, #idea`}
@@ -47,10 +45,6 @@ export class RTFMarkup extends React.Component<{}> {
                     <b style={{ fontSize: 'larger' }}>{`#, ## ... ###### `}</b>
                     {`  set heading style based on number of '#'s between 1 and 6`}
                 </p>
-                <p>
-                    <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
-                    {`  add hashtag metadata to document. e.g, #idea`}
-                </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`>> `}</b>
                     {`  add a sidebar text document inline`}
@@ -61,7 +55,7 @@ export class RTFMarkup extends React.Component<{}> {
                 </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`cmd-f `}</b>
-                    {`  collapse to an inline footnote)`}
+                    {`  collapse to an inline footnote`}
                 </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`cmd-e `}</b>
@@ -116,20 +110,20 @@ export class RTFMarkup extends React.Component<{}> {
                     {`  start a block of text that begins with a hanging indent`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[:doctitle]] `}</b>
+                    <b style={{ fontSize: 'larger' }}>{`@(doctitle) `}</b>
                     {`  hyperlink to document specified by it’s title`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname]] `}</b>
-                    {`  display value of fieldname`}
+                    <b style={{ fontSize: 'larger' }}>{`[@(doctitle.)fieldname] `}</b>
+                    {`  display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname=value]] `}</b>
-                    {`  assign value to fieldname of document and display it`}
+                    <b style={{ fontSize: 'larger' }}>{`[@fieldname:value] `}</b>
+                    {`  assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc.  if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname:doctitle]] `}</b>
-                    {`  show value of fieldname from doc specified by it’s title`}
+                    <b style={{ fontSize: 'larger' }}>{`[@fieldname:=expression] `}</b>
+                    {`  assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc.  if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`}
                 </p>
             </div>
         );
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index b2076e1a5..d15ab749c 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -113,7 +113,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                 <div
                     className="contextMenu-subMenu-cont"
                     style={{
-                        marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
+                        marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%',
                         marginTop,
                         background: SettingsManager.userBackgroundColor,
                     }}>
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 4d0f1bf00..36a9ce6d0 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -39,6 +39,7 @@
     display: inline-block;
     height: 100%;
     width: 100%;
+    max-height: 250px;
 
     .templateToggle,
     .chromeToggle {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index eed197b0b..e7269df98 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,7 +1,8 @@
-import { action, computed, observable, ObservableSet, runInAction } from 'mobx';
+import { computed, observable, ObservableSet, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
@@ -9,12 +10,10 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu
 import { Docs, DocUtils } from '../documents/Documents';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
-import { undoBatch } from '../util/UndoManager';
 import { CollectionTreeView } from './collections/CollectionTreeView';
 import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
-import { DocData } from '../../fields/DocSymbols';
 
 @observer
 class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
@@ -99,7 +98,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 .filter(key => !noteTypes.some(nt => nt.title === key))
                 .forEach(template => templateMenu.push(<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         return (
-            <ul className="template-list" style={{ display: 'block' }}>
+            <ul className="template-list">
                 {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
                 {templateMenu}
                 <CollectionTreeView
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 763d2e3a6..7dcfd32bd 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -100,9 +100,8 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         if (de.complete.docDragData) {
             const key = this._props.pivotField;
             const castedValue = this.getValue(this.heading);
-            const onLayoutDoc = this.onLayoutDoc(key);
             if (this._props.parent.onInternalDrop(e, de)) {
-                de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
+                key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key)));
             }
             return true;
         }
@@ -128,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
                     return false;
                 }
             }
-            this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
+            key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
             this._heading = castedValue.toString();
             return true;
         }
@@ -155,10 +154,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         this._createEmbeddingSelected = false;
         const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
-        const onLayoutDoc = this.onLayoutDoc(key);
         FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = value;
-        (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading);
+        key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading));
         const docs = this._props.parent.childDocList;
         return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
     };
@@ -167,7 +165,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         action(() => {
             this._createEmbeddingSelected = false;
             const key = this._props.pivotField;
-            this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
+            key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
             if (this._props.parent.colHeaderData && this._props.headingObject) {
                 const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
                 this._props.parent.colHeaderData.splice(index, 1);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 6a3cb759e..641e01b81 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -112,7 +112,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
             if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+            this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
             if (this._props.headingObject) {
                 this._props.headingObject.setHeading(castedValue.toString());
                 this._heading = this._props.headingObject.heading;
@@ -137,7 +137,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         if (!value && !forceEmptyNote) return false;
         const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
-        newDoc[key] = this.getValue(this._props.heading);
+        key && (newDoc[key] = this.getValue(this._props.heading));
         const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
         const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
@@ -148,7 +148,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
 
     @action
     deleteColumn = () => {
-        this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+        this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
         if (this._props.colHeaderData && this._props.headingObject) {
             const index = this._props.colHeaderData.indexOf(this._props.headingObject);
             this._props.colHeaderData.splice(index, 1);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 08c00d696..1b4349f44 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -574,11 +574,21 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
                 <EditableView
                     key="editableView"
-                    contents={'+key:value'}
+                    contents={'+key=value'}
                     height={13}
                     fontSize={12}
                     GetValue={returnEmptyString}
-                    SetValue={value => value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)}
+                    SetValue={input => {
+                        const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/);
+                        if (match) {
+                            const key = match[1];
+                            const assign = match[2];
+                            const val = match[3];
+                            KeyValueBox.SetField(doc, key, assign + val, false);
+                            return true;
+                        }
+                        return false;
+                    }}
                 />
             </div>
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 0579b07c7..3fdc9a488 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,29 +1,26 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
+import { aggregateBounds } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { aggregateBounds } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
-import { undoable, UndoManager } from '../../util/UndoManager';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 // import { InkTranscription } from '../InkTranscription';
+import { DocData } from '../../../fields/DocSymbols';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { WebBox } from '../nodes/WebBox';
 import { VideoBox } from '../nodes/VideoBox';
-import { DocData } from '../../../fields/DocSymbols';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { PrefetchProxy } from '../../../fields/Proxy';
-import { MakeTemplate } from '../../util/DropConverter';
+import { WebBox } from '../nodes/WebBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
@@ -76,19 +73,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
-    if (checkResult) {
-        return Doc.UserDoc().defaultTextLayout;
-    }
-    const view = SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView instanceof FormattedTextBox ? SelectionManager.Views[0] : undefined;
-
-    if (view) {
-        const tempDoc = view.Document;
-        if (!view.layoutDoc.isTemplateDoc) {
-            MakeTemplate(tempDoc);
-        }
-        Doc.UserDoc().defaultTextLayout = new PrefetchProxy(tempDoc);
-        tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', tempDoc);
-    } else Doc.UserDoc().defaultTextLayout = undefined;
+    return DocumentView.setDefaultTemplate(checkResult);
 });
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
@@ -197,7 +182,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     map.get(attr)?.setDoc?.();
 });
 
-type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
+type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
 type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
 
 ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
@@ -221,6 +206,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
     const attrs:attrfuncs[] = [
         ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
                         toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
+        ['elide',      { checkResult: () => false,
+                        toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
         ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
         ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 715b23fb6..62f630c6c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -159,6 +159,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
     remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
     remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
+
+    /**
+     * Tests for whether a comparison box slot (ie, before or after) has renderable text content
+     * @param whichSlot field key for start or end slot
+     * @returns a JSX layout string if a text field is found, othwerise undefined
+     */
+    testForTextFields = (whichSlot: string) => {
+        const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string';
+        const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
+        const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
+        const layoutTemplateString =
+            slotHasText ? FormattedTextBox.LayoutString(whichSlot):
+            whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) :
+                                       altText !== undefined     ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore
+
+        // A bit hacky to try out the concept of using GPT to fill in flashcards
+        //   If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
+        // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.<field>).
+        // eg., this.text_alternate is
+        //     "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
+        //     where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
+        // The GPT call will put the "answer"  in the second slot of the comparison (eg., text_2)
+        if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
+            var queryText = altText.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+            if (queryText && queryText.match(/\(\(.*\)\)/)) {
+                KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
+            }
+        }
+        return layoutTemplateString;
+    };
+
     _closeRef = React.createRef<HTMLDivElement>();
     render() {
         const clearButton = (which: string) => {
@@ -176,48 +207,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         /**
          * Display the Docs in the before/after fields of the comparison.  This also supports a GPT flash card use case
          * where if there are no Docs in the slots, but the main fieldKey contains text, then
-         * @param which
+         * @param whichSlot
          * @returns
          */
-        const displayDoc = (which: string) => {
-            const whichDoc = DocCast(this.dataDoc[which]);
+        const displayDoc = (whichSlot: string) => {
+            const whichDoc = DocCast(this.dataDoc[whichSlot]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
-            const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
-            // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
-            // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot
-            const layoutTemplateString = !targetDoc
-                ? which.endsWith('1') && subjectText !== undefined
-                    ? FormattedTextBox.LayoutString(this.fieldKey)
-                    : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string')
-                      ? FormattedTextBox.LayoutString(which)
-                      : undefined
-                : undefined;
-
-            // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic.
-            //   If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
-            // and the fieldKey + "_alternate" has text, then treat the _alternate's text as a GPT query (indicated by (( && )) ) that is parameterized (optionally)
-            // by the field references in the text (eg., this.text_alternate is
-            //     "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
-            //     where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
-            // A GPT call will put the "answer"  in the second slot of the comparison (eg., text_2)
-            if (which.endsWith('2') && !layoutTemplateString && !targetDoc) {
-                var queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])
-                    ?.Text.replace('(this)', subjectText) // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
-                    .trim();
-                if (subjectText && queryText.match(/\(\(.*\)\)/)) {
-                    KeyValueBox.SetField(this.Document, which, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
-                }
-            }
+            const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot);
             return targetDoc || layoutTemplateString ? (
                 <>
                     <DocumentView
                         {...this._props}
+                        ignoreUsePath={layoutTemplateString ? true : undefined}
                         renderDepth={this.props.renderDepth + 1}
                         LayoutTemplateString={layoutTemplateString}
                         Document={layoutTemplateString ? this.Document : targetDoc}
                         containerViewPath={this.DocumentView?.().docViewPath}
-                        moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
-                        removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
+                        moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
+                        removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         isContentActive={emptyFunction}
@@ -227,7 +234,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         hideLinkButton={true}
                         pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                     />
-                    {layoutTemplateString ? null : clearButton(which)}
+                    {layoutTemplateString ? null : clearButton(whichSlot)}
                 </> // placeholder image if doc is missing
             ) : (
                 <div className="placeholder">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9848f18e0..e9ce98583 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,6 +10,7 @@ import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fi
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
+import { PrefetchProxy } from '../../../fields/Proxy';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -19,10 +20,11 @@ import { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocOptions, DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
+import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
 import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -36,6 +38,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
+import { FieldsDropdown } from '../FieldsDropdown';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
 import { AudioAnnoState, StyleProp } from '../StyleProvider';
@@ -47,7 +50,6 @@ import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
-import { FieldsDropdown } from '../FieldsDropdown';
 interface Window {
     MediaRecorder: MediaRecorder;
 }
@@ -1271,6 +1273,32 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
     }, 'set custom view');
 
+    public static setDefaultTemplate(checkResult?: boolean) {
+        if (checkResult) {
+            return Doc.UserDoc().defaultTextLayout;
+        }
+        const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
+        undoable(() => {
+            var tempDoc: Opt<Doc>;
+            if (view) {
+                if (!view.layoutDoc.isTemplateDoc) {
+                    tempDoc = view.Document;
+                    MakeTemplate(tempDoc);
+                    Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
+                    Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButton(tempDoc));
+                    tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc);
+                } else {
+                    tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
+                    if (!tempDoc) {
+                        tempDoc = view.Document;
+                        while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto);
+                    }
+                }
+            }
+            Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
+        }, 'set default template')();
+    }
+
     /**
      * This switches between the current view of a Doc and a specified alternate layout view.
      * The current view of the Doc is stored in the layout_default field so that it can be restored.
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4ecaaa283..5b47dd91d 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -55,6 +55,7 @@ export interface FieldViewSharedProps {
     ignoreAutoHeight?: boolean;
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
+    ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index f02ad7300..57ae92359 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -5,8 +5,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
-import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -61,23 +60,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     }
 
     @observable noTooltip = false;
-    showTemplate = (): void => {
-        const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
-        dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight);
-    };
-    dragAsTemplate = (): void => {
-        this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
-    };
-    useAsPrototype = (): void => {
-        this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
-    };
 
+    showTemplate = (dragFactory: Doc) => this._props.addDocTab(dragFactory, OpenWhere.addRight);
     specificContextMenu = (): void => {
-        if (!Doc.noviceMode && Cast(this.layoutDoc.dragFactory, Doc, null)) {
-            const cm = ContextMenu.Instance;
-            cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
-            cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
-            cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' });
+        const dragFactory = DocCast(this.layoutDoc.dragFactory);
+        if (!Doc.noviceMode && dragFactory) {
+            ContextMenu.Instance.addItem({ description: 'Show Template', event: () => this.showTemplate(dragFactory), icon: 'tag' });
         }
     };
 
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2bcad806f..d85432631 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -115,7 +115,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                 field === undefined && (field = res.result);
             }
         }
-        if (!key) return field;
+        if (!key) return false;
         if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) {
             target[key] = field;
             return true;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index d59489a78..f9e8ce4f3 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -125,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                                 pinToPres: returnZero,
                             }}
                             GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
-                            SetValue={(value: string) => (KeyValueBox.SetField(this._props.doc, this._props.keyName, value) ? true : false)}
+                            SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
                         />
                     </div>
                 </td>
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 62cb460c2..6b66d829c 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
@@ -8,12 +8,12 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast } from '../../../../fields/Types';
+import { Cast, DocCast } from '../../../../fields/Types';
 import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
 import { FilterPanel } from '../../FilterPanel';
@@ -21,6 +21,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
+import { DocData } from '../../../../fields/DocSymbols';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -62,6 +63,8 @@ export class DashFieldView {
                 height={node.attrs.height}
                 hideKey={node.attrs.hideKey}
                 editable={node.attrs.editable}
+                expanded={node.attrs.expanded}
+                dataDoc={node.attrs.dataDoc}
                 tbox={tbox}
             />
         );
@@ -89,6 +92,8 @@ interface IDashFieldViewInternal {
     width: number;
     height: number;
     editable: boolean;
+    expanded: boolean;
+    dataDoc: boolean;
     node: any;
     getPos: any;
     unclickable: () => boolean;
@@ -101,18 +106,19 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     _fieldKey: string;
     _fieldRef = React.createRef<HTMLDivElement>();
     @observable _dashDoc: Doc | undefined = undefined;
-    @observable _expanded = false;
+    @observable _expanded = this._props.expanded;
 
     constructor(props: IDashFieldViewInternal) {
         super(props);
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
-        this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
+        this._textBoxDoc = this._props.tbox.Document;
+        const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc);
 
         if (this._props.docId) {
-            DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+            DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
         } else {
-            this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
+            setDoc(this._props.tbox.Document);
         }
     }
 
@@ -126,7 +132,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
-    return100 = () => 100;
+    isRowActive = () => this._expanded && this._props.editable;
+    finishEdit = action(() => (this._expanded = false));
+    selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
 
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
@@ -137,18 +145,18 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     col={0}
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
-                    maxWidth={this._props.hideKey ? undefined : this._props.tbox._props.PanelWidth}
-                    columnWidth={this._props.hideKey ? () => this._props.tbox._props.PanelWidth() - 20 : returnZero}
-                    selectedCell={() => [this._dashDoc!, 0]}
+                    maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
+                    columnWidth={returnZero}
+                    selectedCell={this.selectedCell}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
-                    isRowActive={() => this._expanded && this._props.editable}
+                    isRowActive={this.isRowActive}
                     padding={0}
                     getFinfo={emptyFunction}
                     setColumnValues={returnFalse}
                     allowCRs={true}
                     oneLine={!this._expanded}
-                    finishEdit={action(() => (this._expanded = false))}
+                    finishEdit={this.finishEdit}
                     transform={Transform.Identity}
                     menuTarget={null}
                 />
@@ -173,11 +181,21 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         }
     };
 
+    toggleFieldHide = undoable(
+        action(() => this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])),
+        'hideKey'
+    );
+
+    @computed get _hideKey() {
+        return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey'];
+    }
+
     // clicking on the label creates a pivot view collection of all documents
     // in the same collection.  The pivot field is the fieldKey of this label
     onPointerDownLabelSpan = (e: any) => {
         setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
+            DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
             DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
         });
     };
@@ -188,6 +206,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     };
 
     @computed get values() {
+        if (this._props.expanded) return [];
         const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
 
         return vals.strings.map(facet => ({ value: facet, label: facet }));
@@ -203,9 +222,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     height: this._props.height,
                     pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
-                {this._props.hideKey ? null : (
+                {this._props.hideKey || this._hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
-                        {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+                        {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
                     </span>
                 )}
                 {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
@@ -224,6 +243,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
 export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: DashFieldViewMenu;
     static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+    static toggleFieldHide: () => void = emptyFunction;
     constructor(props: any) {
         super(props);
         DashFieldViewMenu.Instance = this;
@@ -233,6 +253,10 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         DashFieldViewMenu.createFieldView(e);
         DashFieldViewMenu.Instance.fadeOut(true);
     };
+    toggleFieldHide = (e: React.MouseEvent) => {
+        DashFieldViewMenu.toggleFieldHide();
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
 
     @observable _fieldKey = '';
 
@@ -252,6 +276,9 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
                 <button className="antimodeMenu-button" onPointerDown={this.showFields}>
                     <FontAwesomeIcon icon="eye" size="lg" />
                 </button>
+                <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+                    <FontAwesomeIcon icon="bullseye" size="lg" />
+                </button>
             </Tooltip>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fb709818c..2b48494f2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -451,7 +451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             var tr = this._editorView.state.tr as any;
             const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
-            Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+            Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
         }
@@ -958,8 +958,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
                 icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
             });
-        optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+        const help = cm.findByDescription('Help...');
+        const helpItems = help && 'subitems' in help ? help.subitems : [];
+        helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
+        !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
         this._downX = this._downY = Number.NaN;
     };
 
@@ -1239,8 +1242,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                             this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
                             this.tryUpdateScrollHeight();
                         }
-                    } else if (incomingValue?.str) {
-                        selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
+                    } else {
+                        selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
                     }
                 }
             }
@@ -1339,15 +1342,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
                 if (pdfAnchor instanceof Doc) {
                     const dashField = view.state.schema.nodes.paragraph.create({}, [
-                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
+                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
-                                title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
+                                title: StrCast(pdfAnchor.title),
                                 noPreview: true,
-                                docref: false,
+                                docref: true,
+                                fontSize: '8px',
                             }),
-                            view.state.schema.marks.pFontSize.create({ fontSize: '8px' }),
-                            view.state.schema.marks.em.create({}),
                         ]),
                     ]);
 
@@ -1926,11 +1928,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             </div>
         );
     }
-    cycleAlternateText = () => {
-        if (this.layoutDoc._layout_enableAltContentUI) {
-            const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
-            this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
-        }
+    cycleAlternateText = (skipHover?: boolean) => {
+        this.layoutDoc._layout_enableAltContentUI = true;
+        const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+        this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
     };
     @computed get overlayAlternateIcon() {
         const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
@@ -1965,7 +1966,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
     }
     get fieldKey() {
-        const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+        return this._fieldKey;
+    }
+    @computed get _fieldKey() {
+        const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
         return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
     }
     @observable _isHovering = false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index dc2c06701..bee0d72e3 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -318,6 +318,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         });
     }
 
+    elideSelection = () => {
+        const state = this.view?.state;
+        if (!state) return;
+        if (state.selection.empty) return false;
+        const mark = state.schema.marks.summarize.create();
+        const tr = state.tr;
+        tr.addMark(state.selection.from, state.selection.to, mark);
+        const content = tr.selection.content();
+        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+        this.view?.dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        return true;
+    };
+
     toggleNoAutoLinkAnchor = () => {
         if (this.view) {
             const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index c798ae4b3..b97141e92 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -267,7 +267,7 @@ export class RichTextRules {
 
             // toggle alternate text UI  %/
             new InputRule(new RegExp(/%\//), (state, match, start, end) => {
-                setTimeout(this.TextBox.cycleAlternateText);
+                setTimeout(() => this.TextBox.cycleAlternateText(true));
                 return state.tr.deleteRange(start, end);
             }),
 
@@ -283,40 +283,26 @@ export class RichTextRules {
                     : tr;
             }),
 
-            new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
-                var count = 0; // ignore first return value which will be the notation that chat is pending a result
-                KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
-                    count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
-                    count++;
-                });
-                return null;
-            }),
-
-            // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
-            // [[<fieldKey> : <Doc>]]
-            // [[:docTitle]] => hyperlink
-            // [[fieldKey]] => show field
-            // [[fieldKey{:,=:}=value]] => show field and also set its value
-            // [[fieldKey:docTitle]] => show field of doc
-            new InputRule(
-                new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)((=:|:)?=)([a-z,A-Z_@\?+\-*/\ 0-9\(\)]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
-                (state, match, start, end) => {
-                    const fieldKey = match[1];
-                    const assign = match[2] === '=' ? '' : match[2];
-                    const value = match[4];
-                    const docTitle = match[5]?.replace(':', '');
+            // create a hyperlink to a titled document
+            // @(<doctitle>)
+            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@:\.\? \-0-9]+)\)/), (state, match, start, end) => {
+                const docTitle = match[2];
+                const prefixLength = '@('.length;
+                if (docTitle) {
                     const linkToDoc = (target: Doc) => {
-                        const rstate = this.TextBox.EditorView?.state;
-                        const selection = rstate?.selection.$from.pos;
-                        if (rstate) {
-                            this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+                        const editor = this.TextBox.EditorView;
+                        const selection = editor?.state?.selection.$from.pos;
+                        if (editor) {
+                            const estate = editor.state;
+                            editor.dispatch(estate.tr.setSelection(new TextSelection(estate.doc.resolve(start), estate.doc.resolve(end - prefixLength))));
                         }
 
                         DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
 
-                        const fstate = this.TextBox.EditorView?.state;
-                        if (fstate && selection) {
-                            this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+                        const teditor = this.TextBox.EditorView;
+                        if (teditor && selection) {
+                            const tstate = teditor.state;
+                            teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
                         }
                     };
                     const getTitledDoc = (docTitle: string) => {
@@ -326,32 +312,57 @@ export class RichTextRules {
                         const titledDoc = DocServer.FindDocByTitle(docTitle);
                         return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
                     };
-                    if (!fieldKey) {
-                        if (docTitle) {
-                            const target = getTitledDoc(docTitle);
-                            if (target) {
-                                setTimeout(() => linkToDoc(target));
-                                return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
-                            }
-                        }
-                        return state.tr;
+                    const target = getTitledDoc(docTitle);
+                    if (target) {
+                        setTimeout(() => linkToDoc(target));
+                        return state.tr.insertText(' ').deleteRange(start, start + prefixLength);
                     }
+                }
+                return state.tr;
+            }),
+
+            // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+            // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
+            // [@{this,doctitle,}.fieldKey]
+            new InputRule(
+                new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+                (state, match, start, end) => {
+                    const docTitle = match[1].substring(1).replace(/\.$/, '');
+                    const fieldKey = match[2];
+                    const assign = match[4] === ':' ? (match[4] = '') : match[4];
+                    const value = match[5];
+                    const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
+                    const getTitledDoc = (docTitle: string) => {
+                        if (!DocServer.FindDocByTitle(docTitle)) {
+                            Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+                        }
+                        return DocServer.FindDocByTitle(docTitle);
+                    };
                     // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
                     if (value?.includes(',') && !value.startsWith('((')) {
                         const values = value.split(',');
                         const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
                         this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
                     } else if (value) {
-                        KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign ? undefined: 
-                            (gptval: FieldResult) => this.Document[DocData][fieldKey] = gptval as string ); // prettier-ignore
+                        KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
+                            (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
                     }
-                    const target = getTitledDoc(docTitle);
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+                    const target = docTitle ? getTitledDoc(docTitle) : undefined;
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
                 },
                 { inCode: true }
             ),
 
+            new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
+                var count = 0; // ignore first return value which will be the notation that chat is pending a result
+                KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
+                    count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
+                    count++;
+                });
+                return null;
+            }),
+
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
             // wiki:title
             new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a141ef041..b68acc8f8 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -74,6 +74,7 @@ export const marks: { [index: string]: MarkSpec } = {
             allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
             title: { default: null },
             noPreview: { default: false },
+            fontSize: { default: null },
             docref: { default: false }, // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
         },
         inclusive: false,
@@ -93,14 +94,16 @@ export const marks: { [index: string]: MarkSpec } = {
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
             return node.attrs.docref && node.attrs.title
                 ? [
-                      'div',
+                      'a',
                       ['span', 0],
                       [
                           'span',
                           {
                               ...node.attrs,
                               class: 'prosemirror-attribution',
+                              'data-targethrefs': targethrefs,
                               href: node.attrs.allAnchors[0].href,
+                              style: `font-size: ${node.attrs.fontSize}`,
                           },
                           node.attrs.title,
                       ],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index c9115be90..905146ee2 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -265,6 +265,8 @@ export const nodes: { [index: string]: NodeSpec } = {
             docId: { default: '' },
             hideKey: { default: false },
             editable: { default: true },
+            expanded: { default: null },
+            dataDoc: { default: false },
         },
         leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
         group: 'inline',
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index daae32e9f..4b40d11b9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -445,6 +445,12 @@ export namespace Doc {
     export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
+    export function isTemplateDoc(doc: Doc) {
+        return GetT(doc, 'isTemplateDoc', 'boolean', true);
+    }
+    export function isTemplateForField(doc: Doc) {
+        return GetT(doc, 'isTemplateForField', 'string', true);
+    }
     export function IsDataProto(doc: Doc) {
         return GetT(doc, 'isDataDoc', 'boolean', true);
     }
@@ -642,7 +648,7 @@ export namespace Doc {
         cloneLinks: boolean,
         cloneTemplates: boolean
     ): Promise<Doc> {
-        if (Doc.IsBaseProto(doc) || ((Doc.Get(doc, 'isTemplateDoc', true) || Doc.Get(doc, 'isTemplateForField', true)) && !cloneTemplates)) {
+        if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
             return doc;
         }
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
@@ -735,7 +741,7 @@ export namespace Doc {
                 const docAtKey = DocCast(clone[key]);
                 if (docAtKey && !Doc.IsSystem(docAtKey)) {
                     if (!Array.from(cloneMap.values()).includes(docAtKey)) {
-                        clone[key] = !cloneTemplates && (Doc.Get(docAtKey, 'isTemplateDoc', true) || Doc.Get(docAtKey, 'isTemplateForField', true)) ? docAtKey : cloneMap.get(docAtKey[Id]);
+                        clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
                     } else {
                         repairClone(docAtKey, cloneMap, cloneTemplates, visited);
                     }
@@ -857,7 +863,7 @@ export namespace Doc {
     // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
     export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
         // nothing to do if the layout isn't a template or we don't have a target that's different than the template
-        if (!targetDoc || templateLayoutDoc === targetDoc || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) {
+        if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
             return templateLayoutDoc;
         }
 
@@ -874,7 +880,7 @@ export namespace Doc {
             expandedTemplateLayout = undefined;
             _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
         } else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
-            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) {
+            if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
@@ -910,8 +916,9 @@ export namespace Doc {
             console.log('Warning: GetLayoutDataDocPair childDoc not defined');
             return { layout: childDoc, data: childDoc };
         }
-        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc;
-        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
+        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+        const templateRoot = DocCast(containerDoc?.rootDocument);
+        return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
     }
 
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
@@ -1035,20 +1042,13 @@ export namespace Doc {
     // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
     // This is appropriate if you're trying to create a document that behaves like all
     // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
-    export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
-        const delegateProto = new Doc();
-        delegateProto[Initializing] = true;
-        delegateProto.proto = doc;
-        delegateProto.author = Doc.CurrentUserEmail;
-        delegateProto.isDataDoc = true;
-        title && (delegateProto.title = title);
-        const delegate = new Doc(id, true);
-        delegate[Initializing] = true;
-        delegate.proto = delegateProto;
-        delegate.author = Doc.CurrentUserEmail;
-        delegate[Initializing] = false;
-        delegateProto[Initializing] = false;
-        return delegate;
+    export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) {
+        const ndoc = Doc.ApplyTemplate(doc);
+        if (ndoc) {
+            Doc.GetProto(ndoc).isDataDoc = true;
+            ndoc && (Doc.GetProto(ndoc).proto = doc);
+        }
+        return ndoc;
     }
 
     let _applyCount: number = 0;
@@ -1671,7 +1671,7 @@ ScriptingGlobals.add(function getEmbedding(doc: any) {
     return Doc.MakeEmbedding(doc);
 });
 ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
-    return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto);
+    return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto);
 });
 ScriptingGlobals.add(function copyField(field: any) {
     return Field.Copy(field);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index b73520999..c2ec3f13a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -286,6 +286,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
 // target should be either a Doc or ListImpl.  receiver should be a Proxy<Doc> Or List.
 //
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+    if (!in_prop) {
+        console.log('WARNING: trying to set an empty property.  This should be fixed. ');
+        return false;
+    }
     let prop = in_prop;
     const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
     if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
-- 
cgit v1.2.3-70-g09d2


From 2e0cb3e0a470994eecbb7f6b2ec87296baf517b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 24 Mar 2024 19:04:42 -0400
Subject: fixed linkdocpreviews to sequence through multiple links.  fixed text
 boxes to update text when dashfieldView text changes (but the fieldview
 doesn't), fixed dashFieldViews to be editable cleanly, and to allow
 sub-dashFieldViews to be editbale.  allowed toggle on/off of dashFieldView
 fieldKey.   got rid of sidebars in scemaCells.  fied editing dashFieldViews
 in captions and as childrend of dashFieldViews by passing rootSelected

---
 .../collectionSchema/SchemaTableCell.tsx           |  9 ++++-
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/LinkDocPreview.tsx          |  1 +
 .../views/nodes/formattedText/DashFieldView.scss   | 32 ++++++++++-----
 .../views/nodes/formattedText/DashFieldView.tsx    | 45 ++++++++++++++--------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 26 +++++++++----
 6 files changed, 80 insertions(+), 34 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 711ef507c..ce73ff8a4 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -51,6 +51,8 @@ export interface SchemaTableCellProps {
     options?: string[];
     menuTarget: HTMLDivElement | null;
     transform: () => Transform;
+    autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click
+    rootSelected?: () => boolean;
 }
 
 @observer
@@ -89,6 +91,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             select: emptyFunction,
             dragAction: dropActionType.move,
             renderDepth: 1,
+            noSidebar: true,
             isContentActive: returnFalse,
             whenChildContentsActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
@@ -99,6 +102,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             fieldKey: fieldKey,
             PanelWidth: columnWidth,
             PanelHeight: props.rowHeight,
+            rootSelected: props.rootSelected,
         };
         const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
         const cursor = !readOnly ? 'text' : 'default';
@@ -124,6 +128,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                     pointerEvents,
                 }}>
                 <EditableView
+                    ref={r => this._props.autoFocus && r?.setIsFocused(true)}
                     oneLine={this._props.oneLine}
                     allowCRs={this._props.allowCRs}
                     contents={undefined}
@@ -314,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
         const selected: [Doc, number] | undefined = this._props.selectedCell();
         return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
+
+    // if the text box blurs and none of its contents are focused(), then the edit finishes
     selectedFunc = () => this.selected;
     render() {
         const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
         fieldProps.isContentActive = this.selectedFunc;
         return (
             <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
-                {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+                {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
             </div>
         );
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e9ce98583..1044d6609 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -892,6 +892,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     fieldKey={this.layout_showCaption}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
+                    rootSelected={this.rootSelected}
                     noSidebar={true}
                     dontScale={true}
                     renderDepth={this._props.renderDepth}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ae25ff179..4b6ee7d72 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -172,6 +172,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 if (nextHrefInd !== this._hrefInd) {
                     this._linkDoc = undefined;
                     this._hrefInd = nextHrefInd;
+                    this.updateHref();
                 }
             }),
             true
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 7a0ff8776..74eeb014c 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,20 +1,11 @@
 @import '../../global/globalCssVariables.module.scss';
 
+.dashFieldView-active,
 .dashFieldView {
     position: relative;
     display: inline-flex;
     align-items: center;
 
-    select {
-        display: none;
-    }
-
-    &:hover {
-        select {
-            display: unset;
-        }
-    }
-
     .dashFieldView-enumerables {
         width: 10px;
         height: 10px;
@@ -50,6 +41,27 @@
         }
     }
 }
+
+.dashFieldView,
+.dashFieldView-active {
+    .dashFieldView-select {
+        height: 10p;
+        font-size: 12px;
+        background: transparent;
+        opacity: 0;
+        width: 5px;
+    }
+}
+
+.dashFieldView {
+    &:hover {
+        .dashFieldView-select {
+            opacity: unset;
+            width: 15px !important;
+        }
+    }
+}
+
 .ProseMirror-selectedNode {
     outline: solid 1px $light-blue !important;
 }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 6b66d829c..5b03e2236 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -133,20 +133,29 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         this._reactionDisposer?.();
     }
     isRowActive = () => this._expanded && this._props.editable;
-    finishEdit = action(() => (this._expanded = false));
+
+    finishEdit = action(() => {
+        if (this._expanded) {
+            this._expanded = false;
+            // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus
+            // the timeout allows switching focus from one dashFieldView to another in the same text box
+            setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
+        }
+    });
     selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
+    columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey
 
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
         return !this._dashDoc ? null : (
-            <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
+            <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
                 <SchemaTableCell
                     Document={this._dashDoc}
                     col={0}
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
                     maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
-                    columnWidth={returnZero}
+                    columnWidth={this._expanded ? this.columnWidth : returnZero}
                     selectedCell={this.selectedCell}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
@@ -159,6 +168,8 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     finishEdit={this.finishEdit}
                     transform={Transform.Identity}
                     menuTarget={null}
+                    autoFocus={true}
+                    rootSelected={this._props.tbox._props.rootSelected}
                 />
             </div>
         );
@@ -187,12 +198,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     );
 
     @computed get _hideKey() {
-        return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey'];
+        return this._dashDoc?.[this._fieldKey + '_hideKey'] && !this._expanded;
     }
 
     // clicking on the label creates a pivot view collection of all documents
     // in the same collection.  The pivot field is the fieldKey of this label
-    onPointerDownLabelSpan = (e: any) => {
+    onPointerDownLabelSpan = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
             DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
@@ -215,7 +226,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     render() {
         return (
             <div
-                className="dashFieldView"
+                className={`dashFieldView${this.isRowActive() ? '-active' : ''}`}
                 ref={this._fieldRef}
                 style={{
                     width: this._props.width,
@@ -229,7 +240,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 )}
                 {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
                 {!this.values.length ? null : (
-                    <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+                    <select className="dashFieldView-select" onChange={this.selectVal}>
                         {this.values.map(val => (
                             <option value={val.value}>{val.label}</option>
                         ))}
@@ -272,14 +283,18 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
     render() {
         return this.getElement(
-            <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
-                <button className="antimodeMenu-button" onPointerDown={this.showFields}>
-                    <FontAwesomeIcon icon="eye" size="lg" />
-                </button>
-                <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
-                    <FontAwesomeIcon icon="bullseye" size="lg" />
-                </button>
-            </Tooltip>
+            <>
+                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+                    <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+                        <FontAwesomeIcon icon="eye" size="lg" />
+                    </button>
+                </Tooltip>
+                <Tooltip key="trash" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+                    <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+                        <FontAwesomeIcon icon="bullseye" size="lg" />
+                    </button>
+                </Tooltip>
+            </>
         );
     }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2b48494f2..793595694 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -21,7 +21,7 @@ import { List } from '../../../../fields/List';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
@@ -68,8 +68,13 @@ import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
+
+interface FormattedTextBoxProps extends FieldViewProps {
+    onBlur?: () => void; // callback when text loses focus
+    autoFocus?: boolean; // whether text should get input focus  when created
+}
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -200,7 +205,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    constructor(props: FieldViewProps) {
+    constructor(props: FormattedTextBoxProps) {
         super(props);
         makeObservable(this);
         FormattedTextBox.Instance = this;
@@ -322,7 +327,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     leafText = (node: Node) => {
         if (node.type === this._editorView?.state.schema.nodes.dashField) {
             const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
-            return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field);
+            const fieldKey = StrCast(node.attrs.fieldKey);
+            return fieldKey + ':' + Field.toJavascriptString(refDoc[fieldKey] as Field);
         }
         return '';
     };
@@ -359,15 +365,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
 
                 let unchanged = true;
-                if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
+                const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes
+                if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                     this._applyingChange = this.fieldKey;
-                    const textChange = newText !== prevData?.Text;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
                     if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
+                        if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
-                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined;
+                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText || DocCast(dataDoc.proto)?.[this.fieldKey] === undefined ? new RichTextField(newJson, newText) : undefined;
+
                             textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
                             this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
                             dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
@@ -1483,6 +1490,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             FormattedTextBox.PasteOnLoad = undefined;
             pdfAnchorId && this.addPdfReference(pdfAnchorId);
         }
+        if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
     }
 
     // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -1724,6 +1732,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection());
             }, 'titler');
         }
+        // if the text box blurs and none of its contents are focused(), then pass the blur along
+        setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
     };
 
     onKeyDown = (e: React.KeyboardEvent) => {
-- 
cgit v1.2.3-70-g09d2


From b420caf2c7ecd386cae2cc550904522474b541aa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Mar 2024 22:34:10 -0400
Subject: added empty image tool and click on empty image to select from
 filesystem.   fixed following links in lightbox and showing links to
 stackedTimelines.  fixed embedding docs into text.  fixed not resizing text
 boxes that also show up in pivot view.  prevent context menu from going off
 top of screen.   fixed freeform clustering colors and click to type. fixed
 links to stackedTimeline marks, and titles for marks.  made title editing
 from doc deco and header use same syntax as keyValue.  fixed marquee
 selection on webBoxes.  turn off transitions in freeformdocview after
 timeout. enabled iconifying templates to propagate to "offspring".  fixes
 images in templates.  don't show headr on schema views.

---
 src/client/documents/Documents.ts                  | 52 ++++++++--------
 src/client/util/DocumentManager.ts                 |  4 +-
 src/client/views/ContextMenu.tsx                   |  6 +-
 src/client/views/DocumentDecorations.tsx           | 71 ++++++++--------------
 src/client/views/FieldsDropdown.tsx                |  1 -
 src/client/views/LightboxView.tsx                  |  2 +
 src/client/views/MarqueeAnnotator.tsx              | 19 ++++--
 src/client/views/ObservableReactComponent.tsx      |  3 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 |  6 +-
 .../collections/CollectionStackedTimeline.tsx      | 13 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/global/globalScripts.ts           |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++--
 src/client/views/nodes/DocumentContentsView.tsx    |  7 ++-
 src/client/views/nodes/DocumentView.tsx            | 35 +++++++----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/ImageBox.tsx                | 55 +++++++++++++----
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/LabelBox.tsx                |  2 +-
 src/client/views/nodes/LinkBox.tsx                 | 47 ++++++++++----
 src/client/views/nodes/VideoBox.tsx                |  5 +-
 src/client/views/nodes/WebBox.tsx                  | 65 ++++++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 17 ++++--
 src/client/views/nodes/trails/PresBox.tsx          |  2 +-
 src/fields/Doc.ts                                  | 29 +++++----
 src/fields/PresField.ts                            |  6 --
 src/fields/RichTextField.ts                        |  7 ---
 31 files changed, 311 insertions(+), 195 deletions(-)
 delete mode 100644 src/fields/PresField.ts

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 17cb6fef8..b63c5e429 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1017,8 +1017,8 @@ export namespace Docs {
         }
 
         export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
-            const imgField = url instanceof ImageField ? url : new ImageField(url);
-            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+            const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
+            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function PresDocument(options: DocumentOptions = {}) {
@@ -1950,6 +1950,31 @@ export namespace DocUtils {
         return dd;
     }
 
+    export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+        if (Upload.isImageInformation(result)) {
+            const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+            const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+            proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+            proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+            proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            if (NumCast(proto['data-nativeOrientation']) >= 5) {
+                proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+                proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            }
+            proto.data_exif = JSON.stringify(result.exifData?.data);
+            proto.data_contentSize = result.contentSize;
+            // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+            const latitude = result.exifData?.data?.GPSLatitude;
+            const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+            const longitude = result.exifData?.data?.GPSLongitude;
+            const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+            if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+                proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+                proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+            }
+        }
+    }
+
     async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
         if (result instanceof Error) {
             alert(`Upload failed: ${result.message}`);
@@ -1961,28 +1986,7 @@ export namespace DocUtils {
         if (doc) {
             const proto = Doc.GetProto(doc);
             proto.text = result.rawText;
-            if (Upload.isImageInformation(result)) {
-                const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
-                const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
-                proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
-                proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-                if (NumCast(proto['data-nativeOrientation']) >= 5) {
-                    proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                    proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-                }
-                proto.data_exif = JSON.stringify(result.exifData?.data);
-                proto.data_contentSize = result.contentSize;
-                // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
-                const latitude = result.exifData?.data?.GPSLatitude;
-                const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
-                const longitude = result.exifData?.data?.GPSLongitude;
-                const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
-                if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
-                    proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
-                    proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
-                }
-            }
+            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
             if (Upload.isVideoInformation(result)) {
                 proto.data_duration = result.duration;
             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a38a330da..40d28c690 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -59,7 +59,7 @@ export class DocumentManager {
     private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
     public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
         if (doc) {
-            const dv = this.getDocumentView(doc);
+            const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
             this._viewRenderedCbs.push({ doc, func });
             if (dv) {
                 this.callAddViewFuncs(dv);
@@ -262,7 +262,7 @@ export class DocumentManager {
                     return res(this.getDocumentView(docContextPath[0])!);
                 }
                 options.didMove = true;
-                docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+                (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
                 this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
             }));
         if (options.openLocation === OpenWhere.lightbox) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8f4e43978..ca877b93e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                         this._width = DivWidth(r);
                         this._height = DivHeight(r);
                     }
+                    this._searchRef.current?.focus();
                 })}
                 style={{
                     display: this._display ? '' : 'none',
                     left: this.pageX,
-                    ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
                     background: SettingsManager.userBackgroundColor,
                     color: SettingsManager.userColor,
                 }}>
@@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
             const item = this.flatItems[this._selectedIndex];
             if (item) {
                 item.event({ x: this.pageX, y: this.pageY });
-            } else if (this._searchString.startsWith(this._defaultPrefix)) {
+            } else {
+                //if (this._searchString.startsWith(this._defaultPrefix)) {
                 this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
             }
             this.closeMenu();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fb9f0fc1..4d9b93896 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -34,6 +34,7 @@ import { Colors } from './global/globalEnums';
 import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @observable _showNothing = true;
     @observable private _accumulatedTitle = '';
-    @observable private _titleControlString: string = '#title';
+    @observable private _titleControlString: string = '$title';
     @observable private _editingTitle = false;
     @observable private _hidden = false;
     @observable private _isRotating: boolean = false;
@@ -111,9 +112,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @action
     titleBlur = () => {
-        if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
+        if (this._accumulatedTitle.startsWith('$')) {
             this._titleControlString = this._accumulatedTitle;
-        } else if (this._titleControlString.startsWith('#')) {
+        } else if (this._titleControlString.startsWith('$')) {
             if (this._accumulatedTitle.startsWith('-->#')) {
                 SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
             }
@@ -131,26 +132,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 Doc.AddToMyPublished(d.Document);
                             }
                         }
-                        //@ts-ignore
-                        const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
-
-                        if (titleField.toString().startsWith('<this>')) {
-                            const title = titleField.toString().replace(/<this>\.?/, '');
-                            const curKey = Doc.LayoutFieldKey(d.Document);
-                            if (curKey !== title) {
-                                if (title) {
-                                    if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
-                                        d.Document.layout_fieldKey = `layout_${title}`;
-                                        d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
-                                        d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
-                                    }
-                                } else {
-                                    d.Document.layout_fieldKey = undefined;
-                                }
-                            }
-                        } else {
-                            Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
-                        }
+                        KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
                     }),
                 'edit title'
             );
@@ -181,7 +163,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 e => this.onBackgroundMove(true, e),
                 emptyFunction,
                 action(e => {
-                    !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+                    const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+                    !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
                     this._editingTitle = true;
                     this._keyinput.current && setTimeout(this._keyinput.current.focus);
                 })
@@ -622,11 +605,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     get selectionTitle(): string {
         if (SelectionManager.Views.length === 1) {
             const selected = SelectionManager.Views[0];
-            if (this._titleControlString.startsWith('=')) {
-                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })?.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
-            }
-            if (this._titleControlString.startsWith('#')) {
-                return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+            if (this._titleControlString.startsWith('$')) {
+                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
             }
             return this._accumulatedTitle;
         }
@@ -731,21 +711,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             ) : null;
 
         const titleArea = this._editingTitle ? (
-            <input
-                ref={this._keyinput}
-                className="documentDecorations-title"
-                type="text"
-                name="dynbox"
-                autoComplete="on"
-                value={hideTitle ? '' : this._accumulatedTitle}
-                onBlur={action((e: React.FocusEvent) => {
-                    this._editingTitle = false;
-                    !hideTitle && this.titleBlur();
-                })}
-                onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
-                onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
-                onPointerDown={e => e.stopPropagation()}
-            />
+            <>
+                {r - x < 150 ? null : <span>{this._titleControlString + ':'}</span>}
+                <input
+                    ref={this._keyinput}
+                    className="documentDecorations-title"
+                    type="text"
+                    name="dynbox"
+                    autoComplete="on"
+                    value={hideTitle ? '' : this._accumulatedTitle}
+                    onBlur={action((e: React.FocusEvent) => {
+                        this._editingTitle = false;
+                        !hideTitle && this.titleBlur();
+                    })}
+                    onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+                    onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+                    onPointerDown={e => e.stopPropagation()}
+                />
+            </>
         ) : (
             <div className="documentDecorations-title" key="title">
                 {hideTitle ? null : (
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 5638d34c6..6a5c2cb4c 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
             .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
         const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
 
-        console.log(options);
         return (
             <Select
                 styles={{
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index c7ff7ce47..b9d805145 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -23,6 +23,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { SnappingManager } from '../util/SnappingManager';
+import { Id } from '../../fields/FieldSymbols';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -247,6 +248,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                     }}>
                     <GestureOverlay isActive={true}>
                         <DocumentView
+                            key={this._doc.title + this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
                             ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
                             Document={this._doc}
                             PanelWidth={this.lightboxWidth}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index bd6be2519..c29474fcd 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps {
     annotationLayer: HTMLDivElement;
     addDocument: (doc: Doc) => boolean;
     getPageFromScroll?: (top: number) => number;
-    finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
+    finishMarquee: (x?: number, y?: number) => void;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
     highlightDragSrcColor?: string;
@@ -168,7 +168,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
     @action
     public onInitiateSelection(down: number[]) {
-        console.log('DOWN = ' + down[0] + ' ' + down[1]);
         this._width = this._height = 0;
         this._start = this.getTransformedScreenPt(down);
 
@@ -240,6 +239,13 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         document.removeEventListener('pointerup', this.onSelectEnd);
     }
 
+    @action
+    onMove = (pt: number[]) => {
+        const movLoc = this.getTransformedScreenPt(pt);
+        this._width = movLoc.x - this._start.x;
+        this._height = movLoc.y - this._start.y;
+    };
+
     @action
     onSelectMove = (e: PointerEvent) => {
         const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
@@ -248,9 +254,12 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged.  if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
     };
 
-    @action
     onSelectEnd = (e: PointerEvent) => {
         e.stopPropagation();
+        this.onEnd(e.clientX, e.clientY);
+    };
+    @action
+    onEnd = (x: number, y: number) => {
         const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
         const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
         if (!this.isEmpty && marqueeStyle) {
@@ -266,9 +275,9 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             copy.style.height = parseInt(marqueeStyle.height.toString().replace('px', '')) / scale + 'px';
             (copy as any).marqueeing = true;
             MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this.top) || 0);
-            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+            AnchorMenu.Instance.jumpTo(x, y);
         }
-        this.props.finishMarquee(this.isEmpty ? e.clientX : undefined, this.isEmpty ? e.clientY : undefined, e);
+        this.props.finishMarquee(this.isEmpty ? x : undefined, this.isEmpty ? y : undefined);
         this._width = this._height = 0;
     };
 
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 9b2b00903..394d1eae2 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,8 +14,7 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
         makeObservable(this);
     }
     componentDidUpdate(prevProps: Readonly<T>): void {
-        Object.keys(prevProps).forEach(action(pkey =>
-            (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+        Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
                 ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
     }
 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index cbd3ff358..195b1a04c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1399,7 +1399,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
         const targZoom = this.sourceAnchor?.followLinkZoom;
         const indent = 30;
-        const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
+        const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
 
         return (
             <>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1adb0d9e5..7a15e67e4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -25,6 +25,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
 import './StyleProvider.scss';
+import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
 
 export enum StyleProp {
     TreeViewIcon = 'treeView_Icon',
@@ -137,14 +138,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
-        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamilyq'], StrCast(Doc.UserDoc().fontFamily));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'],  StrCast(Doc.UserDoc().fontWeight));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
         case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
-        case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
+        case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
                 (doc &&
+                    !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
                     !props?.LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
                     !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 5c47d4b9e..656f850b3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -594,6 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                     <StackedTimelineAnchor
                                         {...this._props}
                                         mark={d.anchor}
+                                        containerViewPath={this._props.containerViewPath}
                                         rangeClickScript={this.rangeClickScript}
                                         rangePlayScript={this.rangePlayScript}
                                         left={left - this._scroll}
@@ -701,6 +702,7 @@ interface StackedTimelineAnchorProps {
     layoutDoc: Doc;
     isDocumentActive?: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
+    containerViewPath?: () => DocumentView[];
     _timeline: HTMLDivElement | null;
     focus: FocusFuncType;
     currentTimecode: () => number;
@@ -796,12 +798,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
         );
     };
 
+    resetTitle = () => {
+        this._props.mark[DocData].title = ComputedField.MakeFunction(
+            `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
+        );
+    };
     // context menu
     contextMenuItems = () => {
         const resetTitle = {
-            script: ScriptField.MakeFunction(
-                `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
-            )!,
+            method: this.resetTitle,
             icon: 'folder-plus',
             label: 'Reset Title',
         };
@@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     TemplateDataDocument={undefined}
-                    containerViewPath={returnEmptyDoclist}
+                    containerViewPath={this._props.containerViewPath}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this._props.styleProvider}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..6484658e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -388,7 +388,7 @@ function normalizeResults(
     minWidth: number,
     extras: ViewDefBounds[]
 ): ViewDefResult[] {
-    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(
         extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1fd453e96..5495735ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -565,7 +565,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
         let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
-            switch (property) {
+            switch (property.split(':')[0]) {
                 case StyleProp.BackgroundColor:
                     const cluster = NumCast(doc?.layout_cluster);
                     if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -590,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     trySelectCluster = (addToSel: boolean) => {
-        if (this._hitCluster !== -1) {
+        if (addToSel && this._hitCluster !== -1) {
             !addToSel && SelectionManager.DeselectAll();
             const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
             this.selectDocuments(eles);
@@ -1288,15 +1288,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const rotation = Cast(_rotation,'number',
             !this.layoutDoc._rotation_jitter ? null
                 : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
+        const childProps = { ...this._props, styleProvider: this.clusterStyleProvider };
         return {
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
             autoDim,
             rotation,
-            color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
-            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
-            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
+            color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
+            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b913e05ad..cb593a993 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -107,7 +107,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const cm = ContextMenu.Instance;
         const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
         if (e.key === '?') {
-            cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+            cm.setDefaultItem('?', (str: string) =>
+                this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
+            );
             cm.displayMenu(this._downX, this._downY, undefined, true);
             e.stopPropagation();
         } else if (e.key === 'u' && this._props.ungroup) {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 67dc508d5..bda8afa41 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -213,7 +213,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
 
     get url() {
         const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+        const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
             .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
             .filter(url => url)
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3fdc9a488..d12639fc7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -87,8 +87,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
         });
     } else {
         Doc.SharingDoc().headingColor = undefined;
-        Doc.GetProto(Doc.SharingDoc()).headingColor = color;
-        Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
+        Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+        Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
     }
 });
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2800ea200..0d0a7c623 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { OmitKeys, numberRange } from '../../../Utils';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
+import { TransitionTimer } from '../../../fields/DocSymbols';
 
 /// Ugh, typescript has no run-time way of iterating through the keys of an interface.  so we need
 /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -91,6 +92,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable AutoDim = this.props.autoDim;
     @observable Transition = this.props.transition;
 
+    componentDidMount(): void {
+        if (this.props.transition && !this.Document[TransitionTimer]) {
+            const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
+            this.Document[TransitionTimer] = setTimeout(
+                action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+                num
+            );
+        }
+    }
+
     componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
         super.componentDidUpdate(prevProps);
         this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
@@ -98,14 +109,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
     // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
-    DataTransition = ()  => this._props.transition; // prettier-ignore
+    DataTransition = ()    => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
     RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
     PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
     PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
 
     styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
-            switch (property) {
+            switch (property.split(':')[0]) {
                 case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
                 case StyleProp.BackgroundColor: return this.BackgroundColor;
                 case StyleProp.Color:           return this.Color;
@@ -224,7 +235,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     //  undefined - this is not activated by a group
     isGroupActive = () => {
         if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
-        const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const backColor = this.BackgroundColor;
+        const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
         return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
     };
     render() {
@@ -237,7 +249,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     width: this.PanelWidth(),
                     height: this.PanelHeight(),
                     transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
-                    transition: this.Transition || StrCast(this.Document.dataTransition),
+                    transition: this.DataTransition(),
                     zIndex: this.ZIndex,
                     display: this.Width ? undefined : 'none',
                 }}>
@@ -251,6 +263,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         styleProvider={this.styleProvider}
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         isGroupActive={this.isGroupActive}
+                        PanelWidth={this.PanelWidth}
+                        PanelHeight={this.PanelHeight}
                     />
                 )}
             </div>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5a326ecb0..e729e2fa2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -7,7 +7,7 @@ import { OmitKeys, Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -128,7 +128,9 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
         if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
-        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+        const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
+        const layoutDoc = tempLayout ?? this.layoutDoc;
+        const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string');
         if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
@@ -154,6 +156,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
             'LayoutTemplate',
             'layoutFieldKey',
             'dontCenter',
+            'DataTransition',
             'contextMenuItems',
             //'onClick', // don't need to omit this since it will be set
             'onDoubleClickScript',
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1044d6609..3154ee434 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Howl } from 'howler';
-import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
@@ -104,12 +104,11 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
     dragWhenActive?: boolean;
     dontHideOnDrag?: boolean;
-    suppressSetHeight?: boolean;
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     DataTransition?: () => string | undefined;
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+    contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
     dragConfig?: (data: DragManager.DocumentDragData) => void;
     dragStarting?: () => void;
     dragEnding?: () => void;
@@ -560,7 +559,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
         );
-        this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
+        this._props
+            .contextMenuItems?.()
+            .forEach(
+                item =>
+                    item.label &&
+                    cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
+            );
 
         if (!this.Document.isFolder) {
             const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
@@ -597,7 +602,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 if (!this.Document.annotationOn) {
                     onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
                     !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
-                    cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+                    !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                 } else if (LinkManager.Links(this.Document).length) {
                     onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
                     onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
@@ -810,8 +815,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     };
     /**
      * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
-     * setting layout_showTitle using the format:   field1[;field2[...][:hover]]
-     * from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
+     * setting layout_showTitle using the format:   field1[:hover]
      **/
     @computed get titleView() {
         const showTitle = this.layout_showTitle?.split(':')[0];
@@ -851,21 +855,26 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         contents={
                             showTitle
                                 .split(';')
-                                .map(field => Field.toString(targetDoc[field.trim()] as Field))
+                                .map(field => Field.toJavascriptString(this.Document[field] as Field))
                                 .join(' \\ ') || '-unset-'
                         }
                         display="block"
                         oneLine={true}
                         fontSize={(this.titleHeight / 15) * 10}
-                        GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
+                        GetValue={() =>
+                            showTitle
+                                .split(';')
+                                .map(field => Field.toKeyValueString(this.Document, field))
+                                .join('\\')
+                        }
                         SetValue={undoBatch((input: string) => {
-                            if (input?.startsWith('#')) {
+                            if (input?.startsWith('$')) {
                                 if (this.layoutDoc.layout_showTitle) {
-                                    this.layoutDoc._layout_showTitle = input?.substring(1);
+                                    this.layoutDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
                                 } else if (!this._props.layout_showTitle) {
-                                    Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+                                    Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
                                 }
-                            } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+                            } else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
                                 KeyValueBox.SetField(targetDoc, showTitle, input);
                             }
                             return true;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5b47dd91d..771856788 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -92,6 +92,7 @@ export interface FieldViewSharedProps {
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
+    suppressSetHeight?: boolean;
 }
 
 /**
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 251235b93..86e8ed60a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,7 +9,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -29,6 +29,12 @@ import { OpenWhere } from './DocumentView';
 import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
+import { Colors } from 'browndash-components';
+import { listSpec } from '../../../fields/Schema';
+import { List } from '../../../fields/List';
+import { url } from 'inspector';
+import { OverlayView } from '../OverlayView';
+import { Networking } from '../../Network';
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -134,7 +140,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
                 added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
                     this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
-                    return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+                    return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
                 }, true);
             } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -259,7 +265,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const lower = url.href.toLowerCase();
         if (url.protocol === 'data') return url.href;
         if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
-        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
 
         const ext = extname(url.href);
         return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     ref={this._overlayIconRef}
                     onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
                     style={{
-                        display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+                        display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
                         width: 'min(10%, 25px)',
                         height: 'min(10%, 25px)',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +317,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts
-            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
-            .filter(url => url)
-            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+        const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
+        const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+        const altpaths =
+            alts
+                ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
+                .filter(url => url)
+                .map(url => this.choosePath(url)) ?? []; // acc  ess the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
-        return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+        return paths.length ? paths : [defaultUrl.href];
     }
 
     @observable _error = '';
@@ -326,7 +334,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @computed get content() {
         TraceMobx();
 
-        const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+        const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
         const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
         const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
         const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -370,6 +378,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
+        if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
         if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
@@ -468,4 +477,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             </div>
         );
     }
+
+    public chooseImage = () => {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.multiple = true;
+        input.accept = 'image/*';
+        input.onchange = async _e => {
+            const file = input.files?.[0];
+            if (file) {
+                const disposer = OverlayView.ShowSpinner();
+                const [{ result }] = await Networking.UploadFilesToServer({ file });
+                if (result instanceof Error) {
+                    alert('Error uploading files - possibly due to unsupported file types');
+                } else {
+                    this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+                }
+                disposer();
+            } else {
+                console.log('No file selected');
+            }
+        };
+        input.click();
+    };
 }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5394c7dbb..78e4435ce 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -113,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                     if (key) target[key] = script.originalScript;
                     return false;
                 }
-                field === undefined && (field = res.result);
+                field === undefined && (field = res.result instanceof Array ? new List<any>(res.result) : res.result);
             }
         }
         if (!key) return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index be20b5934..74e78c671 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get Title() {
-        return Field.toString(this.dataDoc[this.fieldKey] as Field);
+        return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 6e4d0e92a..0809e2ad6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Xarrow from 'react-xarrows';
+import { FieldResult } from '../../../fields/Doc';
 import { DocCss, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
 import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
@@ -22,8 +23,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string = 'link') {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
-    disposer: IReactionDisposer | undefined;
-    @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+    _disposer: IReactionDisposer | undefined;
+    @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
     @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
 
     constructor(props: FieldViewProps) {
@@ -39,11 +40,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
     };
     componentWillUnmount() {
-        this.disposer?.();
+        this._disposer?.();
     }
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        this.disposer = reaction(
+        this._disposer = reaction(
             () => ({ drag: SnappingManager.IsDragging }),
             ({ drag }) => {
                 !LightboxView.Contains(this.DocumentView?.()) &&
@@ -64,12 +65,13 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             }
                         })
                     );
-            },
-            { fireImmediately: true }
+            }
         );
     }
 
     render() {
+        TraceMobx();
+
         if (this._hide) return null;
         const a = this.anchor1;
         const b = this.anchor2;
@@ -92,18 +94,34 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
             const bt = b.getBounds?.transition; //    inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
 
+            var foundParent = false;
+            const getAnchor = (field: FieldResult): Element[] => {
+                const doc = DocCast(field);
+                const ele = document.getElementById(doc[Id]);
+                if (ele?.getBoundingClientRect().width) return [ele];
+                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+                const annoOn = DocCast(doc.annotationOn);
+                if (eles.length || !annoOn) return eles;
+                const pareles = getAnchor(annoOn);
+                foundParent = pareles.length ? true : false;
+                return pareles;
+            };
             //   if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
             //   then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
             //   otherwise, we just use the computed nearest point on the document boundary to the target Document
-            const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
-            const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+            const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1);
+            const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2);
 
-            const aid = targetAhyperlink?.id || a.Document[Id];
-            const bid = targetBhyperlink?.id || b.Document[Id];
-            if (!document.getElementById(aid) || !document.getElementById(bid)) {
+            const container = this.DocumentView?.().containerViewPath?.().lastElement().ContentDiv;
+            const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
+            const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
+            if (!aid || !bid) {
                 setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
                 return null;
             }
+            if (foundParent) {
+                setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 1)));
+            }
 
             if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
@@ -192,6 +210,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </>
             );
         }
+
+        setTimeout(
+            action(() => (this._forceAnimate = this._forceAnimate + 1)),
+            2
+        );
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b2ae7201c..0af0751bf 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -335,7 +335,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
         this._props.addDocument?.(imageSnapshot);
         const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
-        link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
+        // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
     };
 
@@ -373,7 +373,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     getView = (doc: Doc, options: FocusViewOptions) => {
         if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
             if (this.heightPercent === 100) {
-                this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+                // do we want to always open up the timeline when followin a link? kind of clunky visually
+                //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
                 options.didMove = true;
             }
             return this._stackedTimeline.getView(doc, options);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9340edc0..f4d5eef05 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { htmlToText } from 'html-to-text';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as WebRequest from 'web-request';
@@ -340,8 +340,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     _textAnnotationCreator: (() => ObservableMap<number, HTMLDivElement[]>) | undefined;
     savedAnnotationsCreator: () => ObservableMap<number, HTMLDivElement[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
 
+    @action
+    iframeMove = (e: PointerEvent) => {
+        const theclick = this.props
+            .ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+        this._marqueeref.current?.onMove(theclick);
+    };
     @action
     iframeUp = (e: PointerEvent) => {
+        this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+        this.marqueeing = undefined;
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         this._textAnnotationCreator = undefined;
         this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
@@ -358,6 +368,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
                 GPTPopup.Instance.addDoc = this.sidebarAddDocument;
             }
+        } else {
+            const theclick = this.props
+                .ScreenToLocalTransform()
+                .inverse()
+                .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+            if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+            else {
+                if (!(e.target as any)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+                this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+                this.marqueeing = undefined;
+            }
+
+            ContextMenu.Instance.closeMenu();
+            ContextMenu.Instance.setIgnoreEvents(false);
+            if (e?.button === 2 || e?.altKey) {
+                e?.preventDefault();
+                e?.stopPropagation();
+                setTimeout(() => {
+                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
+                    this.specificContextMenu(undefined as any);
+                    this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+                });
+            }
         }
     };
     @action
@@ -400,6 +433,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
     @action
     iframeDown = (e: PointerEvent) => {
+        this._textAnnotationCreator = undefined;
+        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+        if (sel?.empty)
+            sel.empty(); // Chrome
+        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+
         this._props.select(false);
         const theclick = this.props
             .ScreenToLocalTransform()
@@ -409,6 +448,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
         if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
             this.marqueeing = theclick;
+            this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+            this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
             e.preventDefault();
         }
     };
@@ -739,28 +780,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             this.marqueeing = undefined;
         }
     };
-    @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+    @action finishMarquee = (x?: number, y?: number) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.marqueeing = undefined;
-        this._textAnnotationCreator = undefined;
-        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
-        if (sel?.empty)
-            sel.empty(); // Chrome
-        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
-        if (x !== undefined && y !== undefined) {
-            ContextMenu.Instance.closeMenu();
-            ContextMenu.Instance.setIgnoreEvents(false);
-            if (e?.button === 2 || e?.altKey) {
-                e?.preventDefault();
-                e?.stopPropagation();
-                setTimeout(() => {
-                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
-                    this.specificContextMenu(undefined as any);
-                    this.DocumentView?.().onContextMenu(undefined, x, y);
-                });
-            }
-        }
     };
 
     @observable lighttext = false;
@@ -992,6 +1015,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     }
     childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
     @computed get webpage() {
+        TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
         const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
@@ -1071,6 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             : 'none';
     annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
+        TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
         const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 793595694..eb7293054 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -450,16 +450,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+        const oldAutoLinks = LinkManager.Links(this.Document).filter(
+            link => 
+                ((!Doc.isTemplateForField(this.Document) &&
+                    (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+                    (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+                 (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+                link.link_relationship === LinkManager.AutoKeywords
+        ); // prettier-ignore
         if (this._editorView?.state.doc.textContent) {
-            const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
             const f = this._editorView.state.selection.from;
+
             const t = this._editorView.state.selection.to;
             var tr = this._editorView.state.tr as any;
             const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
             Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
-            tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+            tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -624,7 +631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         docId: draggedDoc[Id],
                         float: 'unset',
                     });
-                    if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
+                    if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
                         added = dragData.removeDocument?.(draggedDoc) ? true : false;
                     } else {
                         added = true;
@@ -1814,7 +1821,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
             const scrollHeight = this.ProseRef && proseHeight;
-            if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
+            if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e34144fae..ae6da8fb0 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -703,7 +703,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (pinProps.pinData.temporal) {
                 pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
                 const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
-                pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
+                pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
             }
         }
         if (pinProps?.pinViewport) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 246828709..1bd49cf3f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -45,18 +45,23 @@ export namespace Field {
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
         const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        return !Field.IsField(field)
-            ? key.startsWith('_')
-                ? '='
-                : ''
-            : (onDelegate ? '=' : '') +
-                  (field instanceof ComputedField && showComputedValue
-                      ? field._lastComputedResult
-                      : field instanceof ComputedField
-                        ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
-                        : field instanceof ScriptField
-                          ? `$=${field.script.originalScript}`
-                          : Field.toScriptString(field));
+        const valFunc = (field: Field): string => {
+            const res =
+                field instanceof ComputedField && showComputedValue
+                    ? field._lastComputedResult
+                    : field instanceof ComputedField
+                      ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
+                      : field instanceof ScriptField
+                        ? `$=${field.script.originalScript}`
+                        : Field.toScriptString(field);
+            const resStr = (res + '').replace(/^`(.*)`$/, '$1');
+            return typeof field === 'string' && (+resStr).toString() !== resStr && !Array.from('+-*/.').some(k => Array.from(resStr).includes(k))
+                ? resStr
+                : (res + '') // adjust the key value string to be easier to enter: represent any initial list as an array with []
+                      .trim()
+                      .replace(/^new List\((.*)\)$/, '$1');
+        };
+        return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
     }
     export function toScriptString(field: Field) {
         switch (typeof field) {
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-}
\ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index f5801de73..d0a149506 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,11 +36,4 @@ export class RichTextField extends ObjectField {
     [ToString]() {
         return this.Text;
     }
-
-    public static DashField(fieldKey: string) {
-        return new RichTextField(
-            `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"${fieldKey}","docId":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`,
-            ''
-        );
-    }
 }
-- 
cgit v1.2.3-70-g09d2


From bf6d1973cc81ba695afcca102c7229608faaa7e6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 11:02:57 -0400
Subject: changed dashFieldViews to support Tab'ing between other
 dashFieldviews, changed deleting links to clear out the anchors so that
 linkBoxes will go away more easiliy.  changed funcitonPlot to plot the
 equations that are linked to it.  changed equations to link to functions.
 changed undo and other console logging to only happen when undo docked
 buttons are expanded (visible)

---
 src/client/DocServer.ts                            |  4 +-
 src/client/util/LinkManager.ts                     | 12 +++---
 src/client/util/UndoManager.ts                     | 21 ++++-----
 src/client/views/EditableView.tsx                  |  1 -
 src/client/views/PreviewCursor.tsx                 | 12 +++---
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/TemplateMenu.tsx                  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/linking/LinkMenuGroup.tsx         |  2 +-
 src/client/views/nodes/EquationBox.tsx             |  6 ++-
 src/client/views/nodes/FunctionPlotBox.tsx         | 26 ++++++++---
 src/client/views/nodes/LinkBox.tsx                 | 10 +++--
 src/client/views/nodes/LinkDescriptionPopup.tsx    |  6 ++-
 src/client/views/nodes/ScriptingBox.tsx            |  4 +-
 .../views/nodes/formattedText/DashFieldView.tsx    | 50 ++++++++++++++++------
 .../views/nodes/formattedText/EquationView.tsx     |  7 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++--
 .../views/nodes/formattedText/RichTextRules.ts     |  6 +--
 src/debug/Viewer.tsx                               |  4 +-
 19 files changed, 117 insertions(+), 70 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 6217cf04b..bd60a205c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -65,10 +65,10 @@ export namespace DocServer {
         cacheDocumentIds = newCacheUpdate;
 
         // print out cached docs
-        console.log('Set cached docs = ');
+        Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
         const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
         const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
-        strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+        Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
 
         rp.post(Utils.prepend('/setCacheDocumentIds'), {
             body: {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index cf16c4d6d..608e05fe9 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -152,10 +152,12 @@ export class LinkManager {
         }
     }
     public deleteLink(linkDoc: Doc) {
-        return Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
+        const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
+        linkDoc[DocData].link_anchor_1 = linkDoc[DocData].link_anchor_2 = undefined;
+        return ret;
     }
     public deleteAllLinksOnAnchor(anchor: Doc) {
-        LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
+        LinkManager.Instance.relatedLinker(anchor).forEach(LinkManager.Instance.deleteLink);
     }
 
     public getAllRelatedLinks(anchor: Doc) {
@@ -209,10 +211,10 @@ export class LinkManager {
         const a1 = DocCast(linkDoc.link_anchor_1);
         const a2 = DocCast(linkDoc.link_anchor_2);
         if (linkDoc.link_matchEmbeddings) {
-            return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+            return [a2, a2?.annotationOn].includes(anchor) ? '2' : '1';
         }
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
+        if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
+        if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
         if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
 
         // const a1 = DocCast(linkDoc.link_anchor_1);
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 421855bf3..857ca852f 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,5 @@
 import { observable, action, runInAction } from 'mobx';
-import { Field } from '../../fields/Doc';
+import { Doc, Field } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
 import { Without } from '../../Utils';
 
@@ -97,13 +97,14 @@ export namespace UndoManager {
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
-            console.log(
-                '                         '.slice(0, batchCounter.get()) +
-                    'UndoEvent : ' +
-                    event.prop +
-                    ' = ' +
-                    (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
-            );
+            Doc.MyDockedBtns.linearView_IsOpen &&
+                console.log(
+                    '                         '.slice(0, batchCounter.get()) +
+                        'UndoEvent : ' +
+                        event.prop +
+                        ' = ' +
+                        (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+                );
             currentBatch.push(event);
             tempEvents?.push(event);
         }
@@ -171,7 +172,7 @@ export namespace UndoManager {
     }
 
     export function StartBatch(batchName: string): Batch {
-        console.log('                          '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+        Doc.MyDockedBtns.linearView_IsOpen && console.log('                          '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
         runInAction(() => batchCounter.set(batchCounter.get() + 1));
         if (currentBatch === undefined) {
             currentBatch = [];
@@ -181,7 +182,7 @@ export namespace UndoManager {
 
     const EndBatch = action((batchName: string, cancel: boolean = false) => {
         runInAction(() => batchCounter.set(batchCounter.get() - 1));
-        console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+        Doc.MyDockedBtns.linearView_IsOpen && console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
         if (batchCounter.get() === 0 && currentBatch?.length) {
             if (!cancel) {
                 undoStack.push(currentBatch);
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 4508d00a7..44c8e4e1f 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -232,7 +232,6 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
                     onPointerDown: this.stopPropagation,
                     onClick: this.stopPropagation,
                     onPointerUp: this.stopPropagation,
-                    onKeyPress: this.stopPropagation,
                     value: this._props.autosuggestProps.value,
                     // @ts-ignore
                     onChange: this._props.autosuggestProps.onChange,
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index a94c18295..4b7771f27 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -18,7 +18,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
         return PreviewCursor._instance;
     }
 
-    _onKeyPress?: (e: KeyboardEvent) => void;
+    _onKeyDown?: (e: KeyboardEvent) => void;
     _getTransform?: () => Transform;
     _addDocument?: (doc: Doc | Doc[]) => boolean;
     _addLiveTextDoc?: (doc: Doc) => void;
@@ -32,7 +32,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
         makeObservable(this);
         PreviewCursor._instance = this;
         this._clickPoint = observable([0, 0]);
-        document.addEventListener('keydown', this.onKeyPress);
+        document.addEventListener('keydown', this.onKeyDown);
         document.addEventListener('paste', this.paste, true);
     }
 
@@ -120,7 +120,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
     };
 
     @action
-    onKeyPress = (e: KeyboardEvent) => {
+    onKeyDown = (e: KeyboardEvent) => {
         // Mixing events between React and Native is finicky.
         //if not these keys, make a textbox if preview cursor is active!
         if (
@@ -146,7 +146,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
         ) {
             if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
                 //  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
-                this.Visible && this._onKeyPress?.(e);
+                this.Visible && this._onKeyDown?.(e);
                 ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
             }
         } else if (this.Visible) {
@@ -171,7 +171,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
     public static Show(
         x: number,
         y: number,
-        onKeyPress: (e: KeyboardEvent) => void,
+        onKeyDown: (e: KeyboardEvent) => void,
         addLiveText: (doc: Doc) => void,
         getTransform: () => Transform,
         addDocument: undefined | ((doc: Doc | Doc[]) => boolean),
@@ -181,7 +181,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
         const self = PreviewCursor.Instance;
         if (self) {
             self._clickPoint = [x, y];
-            self._onKeyPress = onKeyPress;
+            self._onKeyDown = onKeyDown;
             self._addLiveTextDoc = addLiveText;
             self._getTransform = getTransform;
             self._addDocument = addDocument || returnFalse;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 195b1a04c..0de44b62b 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -715,7 +715,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     value={value}
                     style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
                     onChange={e => setter(e.target.value)}
-                    onKeyPress={e => e.stopPropagation()}
+                    onKeyDown={e => e.stopPropagation()}
                 />
                 <div className="inputBox-button">
                     <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e7269df98..5df0bea1a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -99,7 +99,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 .forEach(template => templateMenu.push(<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         return (
             <ul className="template-list">
-                {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
+                {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyDown={this.onCustomKeypress} />}
                 {templateMenu}
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cb593a993..6b3a56b0b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -101,7 +101,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     };
 
     @action
-    onKeyPress = (e: KeyboardEvent) => {
+    onKeyDown = (e: KeyboardEvent) => {
         //make textbox and add it to this collection
         // tslint:disable-next-line:prefer-const
         const cm = ContextMenu.Instance;
@@ -311,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
             if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
                 PreviewCursor.Instance.Doc = doc;
-                PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
+                PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
             }
             this.clearSelection();
         }
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 028d3da53..60def5d45 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -58,7 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                       ? DocCast(linkDoc.link_anchor_2)
                       : DocCast(linkDoc.link_anchor_1)
                   : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
-                    LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+                    LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
             return !destDoc || !sourceDoc ? null : (
                 <LinkMenuItem
                     key={linkDoc[Id]}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 50d4c7c78..8362db37a 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { LightboxView } from '../LightboxView';
@@ -80,7 +80,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 _height: 300,
                 backgroundColor: 'white',
             });
-            this._props.addDocument?.(graph);
+            const link = DocUtils.MakeLink(this.Document, graph, { link_relationship: 'function', link_description: 'input' });
+            //this._props.addDocument?.(graph);
+            link && this._props.addDocument?.(link);
             e.stopPropagation();
         }
         if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 2e7a2120e..67445e552 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -7,12 +7,13 @@ import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
+import { LinkManager } from '../../util/LinkManager';
 
 @observer
 export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -33,7 +34,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         reaction(
-            () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
+            () => [this.graphFuncs, this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
             () => this.createGraph()
         );
     }
@@ -45,11 +46,17 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
         if (addAsAnnotation) this.addDocument(anchor);
         return anchor;
     };
+    @computed get graphFuncs() {
+        const links = LinkManager.Instance.getAllRelatedLinks(this.Document)
+            .map(d => LinkManager.getOppositeAnchor(d, this.Document))
+            .filter(d => d)
+            .map(d => d!);
+        return links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
+    }
     createGraph = (ele?: HTMLDivElement) => {
         this._plotEle = ele || this._plotEle;
         const width = this._props.PanelWidth();
         const height = this._props.PanelHeight();
-        const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
         try {
             this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
             this._plot = functionPlot({
@@ -59,7 +66,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
                 xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) },
                 yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) },
                 grid: true,
-                data: fns.map(fn => ({
+                data: this.graphFuncs.map(fn => ({
                     fn,
                     // derivative: { fn: "2 * x", updateOnMouseMove: true }
                 })),
@@ -72,7 +79,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData?.droppedDocuments.length) {
-            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true);
+            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
+                ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
+                if (res) {
+                    const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' });
+                    link && this._props.addDocument?.(link);
+                }
+                return res;
+            }, true);
             !added && e.preventDefault();
             e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             return added;
@@ -104,7 +118,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
                 {this.theGraph}
                 <div
                     style={{
-                        display: this._props.isSelected() ? 'none' : undefined,
+                        display: this._props.isContentActive() ? 'none' : undefined,
                         position: 'absolute',
                         width: '100%',
                         height: '100%',
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 0809e2ad6..8ebcefb35 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -23,7 +23,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string = 'link') {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
-    _disposer: IReactionDisposer | undefined;
+    _disposers: { [name: string]: IReactionDisposer } = {};
     @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
     @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
 
@@ -40,11 +40,15 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
     };
     componentWillUnmount() {
-        this._disposer?.();
+        Object.keys(this._disposers).forEach(key => this._disposers[key]());
     }
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        this._disposer = reaction(
+        this._disposers.deleting = reaction(
+            () => !this.anchor1 || !this.anchor2,
+            empty => empty && this._props.removeDocument?.(this.Document)
+        );
+        this._disposers.dragging = reaction(
             () => ({ drag: SnappingManager.IsDragging }),
             ({ drag }) => {
                 !LightboxView.Contains(this.DocumentView?.()) &&
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 1645d0813..2a96ce458 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -69,8 +69,10 @@ export class LinkDescriptionPopup extends React.Component<{}> {
                 }}>
                 <input
                     className="linkDescriptionPopup-input"
-                    onKeyDown={e => e.stopPropagation()}
-                    onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
+                    onKeyDown={e => {
+                        e.key === 'Enter' && this.onDismiss(true);
+                        e.stopPropagation();
+                    }}
                     value={this.description}
                     placeholder={this.description || '(Optional) Enter link description...'}
                     onChange={e => this.descriptionChanged(e)}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index d9d0dbe3e..8c65fd34e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -670,7 +670,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const definedParameters = !this.compileParams.length ? null : (
             <div className="scriptingBox-plist" style={{ width: '30%' }}>
                 {this.compileParams.map((parameter, i) => (
-                    <div key={i} className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+                    <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                         <EditableView
                             display={'block'}
                             maxHeight={72}
@@ -749,7 +749,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 {!this.compileParams.length || !this.paramsNames ? null : (
                     <div className="scriptingBox-plist">
                         {this.paramsNames.map((parameter: string, i: number) => (
-                            <div key={i} className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+                            <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                                 <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
                                     <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
                                     {this.paramsTypes[i] === 'boolean' ? this.renderEnum(parameter, [true, false]) : null}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 5b03e2236..8802a032f 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
@@ -22,15 +22,20 @@ import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 import { DocData } from '../../../../fields/DocSymbols';
+import { NodeSelection, TextSelection } from 'prosemirror-state';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
     root: any;
     node: any;
     tbox: FormattedTextBox;
+    @observable expanded = false;
+    Expanded = () => this.expanded;
 
     unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        makeObservable(this);
+        const self = this;
         this.node = node;
         this.tbox = tbox;
         this.dom = document.createElement('div');
@@ -38,11 +43,26 @@ export class DashFieldView {
         this.dom.style.height = node.attrs.height;
         this.dom.style.position = 'relative';
         this.dom.style.display = 'inline-block';
-        this.dom.onkeypress = function (e: any) {
+        const tBox = this.tbox;
+        this.dom.onkeypress = function (e: KeyboardEvent) {
             e.stopPropagation();
         };
-        this.dom.onkeydown = function (e: any) {
+        this.dom.onkeydown = function (e: KeyboardEvent) {
             e.stopPropagation();
+            if (e.key === 'Tab') {
+                e.preventDefault();
+                const editor = tbox.EditorView;
+                if (editor) {
+                    const state = editor.state;
+                    for (var i = state.selection.to; i < state.doc.content.size; i++) {
+                        if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
+                            editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
+                            return;
+                        }
+                    }
+                    tBox.setFocus(state.selection.to + 1);
+                }
+            }
         };
         this.dom.onkeyup = function (e: any) {
             e.stopPropagation();
@@ -51,6 +71,8 @@ export class DashFieldView {
             e.stopPropagation();
         };
 
+        this.expanded = node.attrs.expanded;
+
         this.root = ReactDOM.createRoot(this.dom);
         this.root.render(
             <DashFieldViewInternal
@@ -63,7 +85,7 @@ export class DashFieldView {
                 height={node.attrs.height}
                 hideKey={node.attrs.hideKey}
                 editable={node.attrs.editable}
-                expanded={node.attrs.expanded}
+                expanded={this.Expanded}
                 dataDoc={node.attrs.dataDoc}
                 tbox={tbox}
             />
@@ -77,9 +99,11 @@ export class DashFieldView {
         });
     }
     deselectNode() {
+        runInAction(() => (this.expanded = false));
         this.dom.classList.remove('ProseMirror-selectednode');
     }
     selectNode() {
+        setTimeout(() => runInAction(() => (this.expanded = true)), 100);
         this.dom.classList.add('ProseMirror-selectednode');
     }
 }
@@ -92,7 +116,7 @@ interface IDashFieldViewInternal {
     width: number;
     height: number;
     editable: boolean;
-    expanded: boolean;
+    expanded: () => boolean;
     dataDoc: boolean;
     node: any;
     getPos: any;
@@ -106,7 +130,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     _fieldKey: string;
     _fieldRef = React.createRef<HTMLDivElement>();
     @observable _dashDoc: Doc | undefined = undefined;
-    @observable _expanded = this._props.expanded;
+    @observable _expanded = this._props.expanded();
 
     constructor(props: IDashFieldViewInternal) {
         super(props);
@@ -132,7 +156,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
-    isRowActive = () => this._expanded && this._props.editable;
+    isRowActive = () => (this._props.expanded() || this._expanded) && this._props.editable;
 
     finishEdit = action(() => {
         if (this._expanded) {
@@ -155,7 +179,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
                     maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
-                    columnWidth={this._expanded ? this.columnWidth : returnZero}
+                    columnWidth={this._expanded || this._props.expanded() ? this.columnWidth : returnZero}
                     selectedCell={this.selectedCell}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
@@ -164,7 +188,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     getFinfo={emptyFunction}
                     setColumnValues={returnFalse}
                     allowCRs={true}
-                    oneLine={!this._expanded}
+                    oneLine={!this._expanded && !this._props.expanded()}
                     finishEdit={this.finishEdit}
                     transform={Transform.Identity}
                     menuTarget={null}
@@ -217,7 +241,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     };
 
     @computed get values() {
-        if (this._props.expanded) return [];
+        if (this._props.expanded()) return [];
         const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
 
         return vals.strings.map(facet => ({ value: facet, label: facet }));
@@ -239,13 +263,13 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     </span>
                 )}
                 {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
-                {!this.values.length ? null : (
-                    <select className="dashFieldView-select" onChange={this.selectVal}>
+                {/* {!this.values.length ? null : (
+                    <select className="dashFieldView-select" tabIndex={-1} onChange={this.selectVal}>
                         {this.values.map(val => (
                             <option value={val.value}>{val.label}</option>
                         ))}
                     </select>
-                )}
+                )} */}
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b786c5ffb..b90653acc 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -8,6 +8,7 @@ import { StrCast } from '../../../../fields/Types';
 import './DashFieldView.scss';
 import EquationEditor from './EquationEditor';
 import { FormattedTextBox } from './FormattedTextBox';
+import { DocData } from '../../../../fields/DocSymbols';
 
 export class EquationView {
     dom: HTMLDivElement; // container for label and value
@@ -88,7 +89,6 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
                     }
                     e.stopPropagation();
                 }}
-                onKeyPress={e => e.stopPropagation()}
                 style={{
                     position: 'relative',
                     display: 'inline-block',
@@ -96,12 +96,11 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
                     height: this.props.height,
                     background: 'white',
                     borderRadius: '10%',
-                    bottom: 3,
                 }}>
                 <EquationEditor
                     ref={this._ref}
-                    value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
-                    onChange={(str: any) => (this._textBoxDoc[this._fieldKey] = str)}
+                    value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
+                    onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
                     autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
                     autoOperatorNames="sin cos tan"
                     spaceBehavesLikeTab={true}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index eb7293054..70b3b52fd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1616,10 +1616,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.stopPropagation();
         }
     };
-    setFocus = () => {
-        const pos = this._editorView?.state.selection.$from.pos || 1;
-        (this.ProseRef?.children?.[0] as any).focus();
-        setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+    setFocus = (ipos?: number) => {
+        const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1);
+        setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100);
+        setTimeout(() => (this.ProseRef?.children?.[0] as any).focus(), 200);
     };
     @action
     onFocused = (e: React.FocusEvent): void => {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index adc031636..c3eaf4dd2 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -385,10 +385,10 @@ export class RichTextRules {
             }),
 
             // create an inline equation node
-            // eq:<equation>>
-            new InputRule(new RegExp(/%eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => {
+            // %eq
+            new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
                 const fieldKey = 'math' + Utils.GenerateGuid();
-                this.TextBox.dataDoc[fieldKey] = match[1];
+                this.TextBox.dataDoc[fieldKey] = 'y=';
                 const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
                 return tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1)));
             }),
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index f46adef77..ceff01fc2 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -152,7 +152,7 @@ class Viewer extends React.Component {
     };
 
     @action
-    onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+    onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
         if (e.key === 'Enter') {
             DocServer.GetRefField(this.idToAdd).then(
                 action((field: any) => {
@@ -168,7 +168,7 @@ class Viewer extends React.Component {
     render() {
         return (
             <>
-                <input value={this.idToAdd} onChange={this.inputOnChange} onKeyDown={this.onKeyPress} />
+                <input value={this.idToAdd} onChange={this.inputOnChange} onKeyDown={this.onKeyDown} />
                 <div>
                     {this.fields.map((field, index) => (
                         <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>
-- 
cgit v1.2.3-70-g09d2


From 397e152d6450b4904645ebb271691fb01b267c4e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 13:29:55 -0400
Subject: fixed collapsible text box outlines.  fixed ending lists with enter.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +++++++-----
 .../formattedText/ProsemirrorExampleTransfer.ts    | 34 ++++++++++------------
 src/client/views/nodes/formattedText/marks_rts.ts  | 16 ----------
 3 files changed, 26 insertions(+), 42 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 70b3b52fd..1a5e1febf 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1671,10 +1671,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._forceUncollapse = false;
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
-        let olistPos = clickPos?.pos;
+        const clickPosVal = clickPos?.pos || 1; // - (this._editorView?.state.doc.nodeAt(clickPos?.pos || 1)?.type === this._editorView?.state.schema.nodes.paragraph ? 1 : 0);
+        let olistPos = clickPosVal;
         if (clickPos && olistPos && this._props.rootSelected?.()) {
-            const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
-            const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
+            const clickNode = this._editorView?.state.doc.resolve(olistPos).node();
+            const nodeBef = this._editorView?.state.doc.resolve(Math.max(0, olistPos - 1)).node();
             olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
             let $olistPos = this._editorView?.state.doc.resolve(olistPos);
             let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
@@ -1684,18 +1685,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            const listPos = this._editorView?.state.doc.resolve(clickPos.pos);
-            const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
+            const listPos = this._editorView?.state.doc.resolve(clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal);
+            const listNode = listPos?.node();
             if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
                 if (!highlightOnly) {
                     if (selectOrderedList) {
                         this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
                     } else {
-                        const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
-                        this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
+                        const nodePos = clickPosVal - (olistPos === clickPosVal ? 0 : 1);
+                        const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
+                        this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
                     }
                 }
-                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' });
+                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'gray' });
             }
         }
     }
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 47527847b..1058c51da 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,7 +1,7 @@
 import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
 import { redo, undo } from 'prosemirror-history';
 import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList } from 'prosemirror-schema-list';
+import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
@@ -11,8 +11,9 @@ import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { OpenWhere } from '../DocumentView';
-import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+//import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
 import { Doc } from '../../../../fields/Doc';
+import { EditorView } from 'prosemirror-view';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
 
@@ -120,7 +121,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
 
         if (
-            !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+            !liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
                 const tx3 = updateBullets(tx2, schema);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
@@ -164,12 +165,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         SelectionManager.DeselectAll();
     });
 
-    const splitMetadata = (marks: any, tx: Transaction) => {
-        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        return tx;
-    };
-
     bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
     bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
     bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
@@ -288,7 +283,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     //command to break line
-    bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+
+    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
@@ -304,7 +300,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         const cr = state.selection.$from.node().textContent.endsWith('\n');
         if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
-            if (
+            if (!view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
+                do {
+                    liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+                } while (view.state.selection.$from.depth > 1);
+            } else if (
                 !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
                     const tx3 = updateBullets(tx2, schema);
                     marks && tx3.ensureMarks([...marks]);
@@ -318,10 +318,9 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                         const tonode = tx3.selection.$to.node();
                         if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
                             const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
-                            splitMetadata(marks, tx4);
-                            if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) {
-                                dispatch(tx4);
-                            }
+                            dispatch(tx4);
+                            if (!view.state.selection.$from.node().content.size) liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+                            else enter(view.state, view.dispatch, view); // view.dispatch(view.state.tr.insertText('\r\n'));
                         } else dispatch(tx3.insertText('\r\n'));
                     })
                 ) {
@@ -330,13 +329,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             }
         }
         return true;
-    });
+    };
+    bind('Enter', enter);
 
     //Command to create a blank space
     bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        dispatch(splitMetadata(marks, state.tr));
         return false;
     });
 
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index b68acc8f8..ccf7de4a1 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -235,22 +235,6 @@ export const marks: { [index: string]: MarkSpec } = {
         },
     },
 
-    metadata: {
-        toDOM() {
-            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
-        },
-    },
-    metadataKey: {
-        toDOM() {
-            return ['span', { style: 'font-style:italic; ' }];
-        },
-    },
-    metadataVal: {
-        toDOM() {
-            return ['span'];
-        },
-    },
-
     summarizeInclusive: {
         parseDOM: [
             {
-- 
cgit v1.2.3-70-g09d2


From 3bf4c1e7e9e34b2f4730e3df504ef06c36d05a9e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 13:39:36 -0400
Subject: fixed backspacing to delete list items.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx          | 2 +-
 src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1a5e1febf..729c4d534 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1671,7 +1671,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._forceUncollapse = false;
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
-        const clickPosVal = clickPos?.pos || 1; // - (this._editorView?.state.doc.nodeAt(clickPos?.pos || 1)?.type === this._editorView?.state.schema.nodes.paragraph ? 1 : 0);
+        const clickPosVal = clickPos?.pos || 1;
         let olistPos = clickPosVal;
         if (clickPos && olistPos && this._props.rootSelected?.()) {
             const clickNode = this._editorView?.state.doc.resolve(olistPos).node();
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 1058c51da..7b4d8aa03 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -255,7 +255,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
-    bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
@@ -267,6 +267,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             if (
                 !joinBackward(state, (tx: Transaction) => {
                     dispatch(updateBullets(tx, schema));
+                    if (!view.state.selection.$from.node().content.size) backspace(view.state, view.dispatch, view);
                 })
             ) {
                 if (
@@ -279,7 +280,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             }
         }
         return true;
-    });
+    };
+    bind('Backspace', backspace);
 
     //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     //command to break line
-- 
cgit v1.2.3-70-g09d2


From 1d47f6cc8be84ab368ad91f287909ee162d1f2e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 15:42:12 -0400
Subject: fixed toggling footnotes.  fixed error in bullet hit test.  fixed
 problems with backspace and enter in prosemirror transfer.  fixed display of
 markdown options to start at top.

---
 src/client/util/RTFMarkup.tsx                      |  2 +-
 .../views/nodes/formattedText/FootnoteView.tsx     |  4 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 41 ++++++++++++++--------
 4 files changed, 32 insertions(+), 18 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 315daad42..57485d893 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -132,7 +132,7 @@ export class RTFMarkup extends React.Component<{}> {
     render() {
         return (
             <MainViewModal
-                dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }}
+                dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
                 contents={this.cheatSheet}
                 isDisplayed={this.isOpen}
                 interactive={true}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index cf48e1250..b327e5137 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -23,6 +23,7 @@ export class FootnoteView {
         this.dom = document.createElement('footnote');
 
         this.dom.addEventListener('pointerup', this.toggle, true);
+        this.dom.addEventListener('mouseup', (e: MouseEvent) => e.stopPropagation(), true);
         // These are used when the footnote is selected
         this.innerView = null;
     }
@@ -82,9 +83,10 @@ export class FootnoteView {
         document.removeEventListener('pointerup', this.ignore, true);
     };
 
-    toggle = () => {
+    toggle = (e: PointerEvent) => {
         if (this.innerView) this.close();
         else this.open();
+        e.stopPropagation();
     };
 
     close() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 729c4d534..9882a9be9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1685,7 +1685,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            const listPos = this._editorView?.state.doc.resolve(clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal);
+            const maxSize = this._editorView?.state.doc.content.size ?? 0;
+            const listPos = this._editorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal));
             const listNode = listPos?.node();
             if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
                 if (!highlightOnly) {
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 7b4d8aa03..87eee1b2f 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -255,7 +255,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
-    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
@@ -267,7 +267,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             if (
                 !joinBackward(state, (tx: Transaction) => {
                     dispatch(updateBullets(tx, schema));
-                    if (!view.state.selection.$from.node().content.size) backspace(view.state, view.dispatch, view);
+                    if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) backspace(view.state, view.dispatch, view, false);
                 })
             ) {
                 if (
@@ -286,26 +286,26 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     //command to break line
 
-    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
         const trange = state.selection.$from.blockRange(state.selection.$to);
-        const path = (state.selection.$from as any).path;
-        const depth = trange ? liftTarget(trange) : undefined;
-        const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item;
-        if (split && trange && depth !== undefined && depth !== null) {
+        const depth = trange ? liftTarget(trange) : null;
+        if (
+            depth !== null &&
+            state.selection.$from.node(state.selection.$from.depth - 1)?.type === state.schema.nodes.blockquote && //
+            !state.selection.$from.node().content.size &&
+            trange
+        ) {
             dispatch(state.tr.lift(trange, depth) as any);
             return true;
         }
 
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        const cr = state.selection.$from.node().textContent.endsWith('\n');
-        if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
-            if (!view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
-                do {
-                    liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
-                } while (view.state.selection.$from.depth > 1);
+        if (!newlineInCode(state, dispatch as any)) {
+            if (once && view.state.selection.$from.depth > 1 && !view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
+                for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
             } else if (
                 !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
                     const tx3 = updateBullets(tx2, schema);
@@ -321,8 +321,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                         if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
                             const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
                             dispatch(tx4);
-                            if (!view.state.selection.$from.node().content.size) liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
-                            else enter(view.state, view.dispatch, view); // view.dispatch(view.state.tr.insertText('\r\n'));
+                            if (
+                                view.state.selection.$from.parentOffset && //
+                                !view.state.selection.$from.node().content.size
+                            )
+                                liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+                            else if (
+                                once &&
+                                view.state.selection.$from.parentOffset &&
+                                view.state.selection.$from.depth > 1 && //
+                                view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === schema.nodes.list_item
+                            )
+                                enter(view.state, view.dispatch, view, false);
+                            else if (once && depth && !view.state.selection.$from.parentOffset) backspace(view.state, view.dispatch, view, false);
                         } else dispatch(tx3.insertText('\r\n'));
                     })
                 ) {
-- 
cgit v1.2.3-70-g09d2


From 9b2cfea0c13fef048ba6ae3d5281d1eea836e9b3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 19:22:26 -0400
Subject: preserve nodeSelections after onBlur by not calling autoLink.  fix
 promoting text to list cursor locatoin

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 22 +++++++++++++---------
 .../formattedText/ProsemirrorExampleTransfer.ts    |  3 ++-
 2 files changed, 15 insertions(+), 10 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9882a9be9..d25101844 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1694,8 +1694,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
                     } else {
                         const nodePos = clickPosVal - (olistPos === clickPosVal ? 0 : 1);
-                        const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
-                        this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
+                        if (this._editorView.state.doc.nodeAt(nodePos)) {
+                            const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
+                            this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
+                        }
                     }
                 }
                 addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'gray' });
@@ -1715,13 +1717,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
         if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
             const stordMarks = this._editorView?.state.storedMarks?.slice();
-            this.autoLink();
-            if (this._editorView?.state.tr) {
-                const tr = stordMarks?.reduce((tr, m) => {
-                    tr.addStoredMark(m);
-                    return tr;
-                }, this._editorView.state.tr);
-                tr && this._editorView.dispatch(tr);
+            if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
+                this.autoLink();
+                if (this._editorView?.state.tr) {
+                    const tr = stordMarks?.reduce((tr, m) => {
+                        tr.addStoredMark(m);
+                        return tr;
+                    }, this._editorView.state.tr);
+                    tr && this._editorView.dispatch(tr);
+                }
             }
         }
         if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 87eee1b2f..ab49a53ea 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -107,7 +107,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                     // when promoting to a list, assume list will format things so don't copy the stored marks.
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
-                    dispatch(tx3);
+                    const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2)));
+                    dispatch(tx4);
                 })
             ) {
                 console.log('bullet promote fail');
-- 
cgit v1.2.3-70-g09d2


From 1b592f74a7df8f6dd7b2881032725f26aedff403 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Mar 2024 11:38:56 -0400
Subject: fixed keyvaluebox to show props document, never the doc in the
 fieldKey slot.  changed computedFIelds to do mobx caching.  changed text
 boxes to do updating from templates based on a fieldKey_autoUpdate flag
 combined with modification timestamps.  enabled comparison box to work with
 text fields in addition to docs.

---
 src/client/util/Scripting.ts                       |  9 +--
 src/client/views/InkControlPtHandles.tsx           | 85 ++++++++++++----------
 src/client/views/PropertiesButtons.tsx             |  4 +-
 src/client/views/nodes/ComparisonBox.tsx           |  6 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 31 +++++---
 src/fields/Doc.ts                                  |  2 +-
 src/fields/ScriptField.ts                          | 22 ++++--
 8 files changed, 90 insertions(+), 71 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index f5e162d16..31222aa50 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -88,15 +88,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
             }
 
             const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
-            if (batch) {
-                batch.end();
-            }
-
+            batch?.end();
             return { success: true, result };
         } catch (error) {
-            if (batch) {
-                batch.end();
-            }
+            batch?.end();
             onError?.(script + ' ' + error);
             return { success: false, error, result: errorVal };
         }
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 31b13d2c8..01d52135a 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { ControlPoint, InkData, PointData } from '../../fields/InkField';
@@ -13,6 +13,7 @@ import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { SnappingManager } from '../util/SnappingManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 export interface InkControlProps {
     inkDoc: Doc;
@@ -24,10 +25,15 @@ export interface InkControlProps {
 }
 
 @observer
-export class InkControlPtHandles extends React.Component<InkControlProps> {
+export class InkControlPtHandles extends ObservableReactComponent<InkControlProps> {
     @observable private _overControl = -1;
     get docView() {
-        return this.props.inkView.DocumentView?.();
+        return this._props.inkView.DocumentView?.();
+    }
+
+    constructor(props: InkControlProps) {
+        super(props);
+        makeObservable(this);
     }
 
     componentDidMount() {
@@ -42,51 +48,51 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
      */
     @action
     onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
-        const ptFromScreen = this.props.inkView.ptFromScreen;
+        const ptFromScreen = this._props.inkView.ptFromScreen;
         if (ptFromScreen) {
             const order = controlIndex % 4;
-            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
-            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
-            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
+            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
+            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this._props.inkCtrlPoints.length;
+            const brokenIndices = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'));
             const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
             if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1;
-            const origInk = this.props.inkCtrlPoints.slice();
+            const origInk = this._props.inkCtrlPoints.slice();
             setupMoveUpEvents(
                 this,
                 e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
-                    if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+                    if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
                     const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
                     const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
                     this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
                     return false;
                 }),
                 action(() => {
-                    if (this.props.inkView.controlUndo && this.docView) {
+                    if (this._props.inkView.controlUndo && this.docView) {
                         InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
                     }
-                    this.props.inkView.controlUndo?.end();
-                    this.props.inkView.controlUndo = undefined;
+                    this._props.inkView.controlUndo?.end();
+                    this._props.inkView.controlUndo = undefined;
                     UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
                 }),
                 action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                    const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+                    const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
                     if (doubleTap || e.button === 2) {
                         if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
                             if (brokenIndices) brokenIndices.push(controlIndex);
-                            else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
+                            else this._props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
-                                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+                                if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
                                 this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
-                                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+                                if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
                                 this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
-                        this.props.inkView.controlUndo?.end();
-                        this.props.inkView.controlUndo = undefined;
+                        this._props.inkView.controlUndo?.end();
+                        this._props.inkView.controlUndo = undefined;
                     }
                     this.changeCurrPoint(controlIndex);
                 }),
@@ -126,14 +132,14 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
 
     render() {
         // Accessing the current ink's data and extracting all control points.
-        const scrData = this.props.screenCtrlPoints;
+        const scrData = this._props.screenCtrlPoints;
         const sreenCtrlPoints: ControlPoint[] = [];
         for (let i = 0; i <= scrData.length - 4; i += 4) {
             sreenCtrlPoints.push({ ...scrData[i], I: i });
             sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
         }
 
-        const inkData = this.props.inkCtrlPoints;
+        const inkData = this._props.inkCtrlPoints;
         const inkCtrlPts: ControlPoint[] = [];
         for (let i = 0; i <= inkData.length - 4; i += 4) {
             inkCtrlPts.push({ ...inkData[i], I: i });
@@ -141,23 +147,23 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
         }
 
         const closed = InkingStroke.IsClosed(inkData);
-        const nearestScreenPt = this.props.nearestScreenPt();
+        const nearestScreenPt = this._props.nearestScreenPt();
         const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle');
         const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => {
-            const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
+            const broken = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
             const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
             return (
                 <Tag
                     key={control.I.toString() + scale}
-                    x={control.X - this.props.screenSpaceLineWidth * 2 * scale}
-                    y={control.Y - this.props.screenSpaceLineWidth * 2 * scale}
+                    x={control.X - this._props.screenSpaceLineWidth * 2 * scale}
+                    y={control.Y - this._props.screenSpaceLineWidth * 2 * scale}
                     cx={control.X}
                     cy={control.Y}
-                    r={this.props.screenSpaceLineWidth * 2 * scale}
-                    opacity={this.props.inkView.controlUndo ? 0.35 : 1}
-                    height={this.props.screenSpaceLineWidth * 4 * scale}
-                    width={this.props.screenSpaceLineWidth * 4 * scale}
-                    strokeWidth={this.props.screenSpaceLineWidth / 2}
+                    r={this._props.screenSpaceLineWidth * 2 * scale}
+                    opacity={this._props.inkView.controlUndo ? 0.35 : 1}
+                    height={this._props.screenSpaceLineWidth * 4 * scale}
+                    width={this._props.screenSpaceLineWidth * 4 * scale}
+                    strokeWidth={this._props.screenSpaceLineWidth / 2}
                     stroke={Colors.MEDIUM_BLUE}
                     fill={broken ? Colors.MEDIUM_BLUE : color}
                     onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)}
@@ -170,7 +176,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
         };
         return (
             <svg>
-                {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this.props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
+                {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
                 {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
             </svg>
         );
@@ -185,10 +191,15 @@ export interface InkEndProps {
     endPt: () => PointData;
 }
 @observer
-export class InkEndPtHandles extends React.Component<InkEndProps> {
+export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
     @observable _overStart: boolean = false;
     @observable _overEnd: boolean = false;
 
+    constructor(props: InkEndProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     _throttle = 0; // need to throttle dragging since the position may change when the control points change.  this allows the stroke to settle so that we don't get increasingly bad jitter
     @action
     dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
@@ -198,7 +209,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
             e,
             action(e => {
                 if (this._throttle++ % 2 !== 0) return false;
-                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
+                if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
                 // compute stretch factor by finding scaling along axis between start and end points
                 const p1 = pt1();
                 const p2 = pt2();
@@ -216,8 +227,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
             }),
             action(() => {
                 SnappingManager.SetIsDragging(false);
-                this.props.inkView.controlUndo?.end();
-                this.props.inkView.controlUndo = undefined;
+                this._props.inkView.controlUndo?.end();
+                this._props.inkView.controlUndo = undefined;
                 UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
             }),
             returnFalse
@@ -230,7 +241,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
                 key={key}
                 cx={pt.X}
                 cy={pt.Y}
-                r={this.props.screenSpaceLineWidth * 2}
+                r={this._props.screenSpaceLineWidth * 2}
                 fill={this._overStart ? '#aaaaaa' : '#99999977'}
                 stroke={'#00007777'}
                 strokeWidth={0}
@@ -242,8 +253,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
         );
         return (
             <svg>
-                {hdl('start', this.props.startPt(), e => this.dragRotate(e, this.props.startPt, this.props.endPt))}
-                {hdl('end', this.props.endPt(), e => this.dragRotate(e, this.props.endPt, this.props.startPt))}
+                {hdl('start', this._props.startPt(), e => this.dragRotate(e, this._props.startPt, this._props.endPt))}
+                {hdl('end', this._props.endPt(), e => this.dragRotate(e, this._props.endPt, this._props.startPt))}
             </svg>
         );
     }
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 02f288a68..517a80d63 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -87,7 +87,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     containerDoc._isLightbox = !containerDoc._isLightbox;
                     //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
                     const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
-                    //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+                    //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
                     containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
                 });
             }
@@ -219,7 +219,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     //                 containerDoc._isLightbox = !containerDoc._isLightbox;
     //                 //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
     //                 const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
-    //                 //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+    //                 //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
     //                 containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
     //             });
     //         }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 59d99b371..9ffdc350d 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -69,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 action((e, doubleTap) => {
                     if (doubleTap) {
                         this._isAnyChildContentActive = true;
-                        if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
-                        if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+                        if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+                        if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
                     }
                 }),
                 false,
@@ -131,8 +131,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return false;
     };
 
-    whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
-
     closeDown = (e: React.PointerEvent, which: string) => {
         setupMoveUpEvents(
             this,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 78e4435ce..31a2367fc 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -55,7 +55,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
     @observable _splitPercentage = 50;
 
     get fieldDocToLayout() {
-        return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
+        return DocCast(this._props.Document);
     }
 
     @action
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d25101844..f06e5fad0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -21,7 +21,7 @@ import { List } from '../../../../fields/List';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
@@ -349,6 +349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
             const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
             const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+            const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
             const effectiveAcl = GetEffectiveAcl(dataDoc);
 
             const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, '');
@@ -369,22 +370,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                     this._applyingChange = this.fieldKey;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
-                    if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
+                    if ((!prevData && !protoData && !layoutData) || newText || (!newText && !protoData && !layoutData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
                         if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
-                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText || DocCast(dataDoc.proto)?.[this.fieldKey] === undefined ? new RichTextField(newJson, newText) : undefined;
-
+                            dataDoc[this.fieldKey] =
+                                numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined;
                             textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
                             this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
-                            dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
                             unchanged = false;
                         }
                     } else {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data)));
-                        dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((layoutData ?? protoData ?? prevData).Data)));
                         ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
                         unchanged = false;
                     }
@@ -958,6 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
+        optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
         optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
         optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
         this._props.renderDepth &&
@@ -1244,9 +1244,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         );
         this._disposers.editorState = reaction(
             () => {
-                const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
-                const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc;
-                return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) };
+                const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey];
+                const dataData = this.dataDoc[this.fieldKey];
+                const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
+                const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+                const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+                const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+                const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
+                const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
+                return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
             },
             incomingValue => {
                 if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1256,11 +1262,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
                             this.tryUpdateScrollHeight();
                         }
-                    } else {
+                    } else if (this._editorView.state.doc.textContent !== incomingValue?.str) {
                         selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
                     }
                 }
-            }
+            },
+            { fireImmediately: true }
         );
 
         this._disposers.search = reaction(
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bdf600475..200896e25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -48,7 +48,7 @@ export namespace Field {
         const valFunc = (field: Field): string => {
             const res =
                 field instanceof ComputedField && showComputedValue
-                    ? field._lastComputedResult
+                    ? field.value(doc)
                     : field instanceof ComputedField
                       ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
                       : field instanceof ScriptField
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 9021c8896..8b51088b2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -198,14 +198,22 @@ export class ComputedField extends ScriptField {
     }
 
     _lastComputedResult: FieldResult;
-    value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
-    _valueOutsideReaction = (doc: Doc) => {
-        this._lastComputedResult =
-            this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
-        return this._lastComputedResult;
-    };
+    value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ?? 
+        computedFn((doc: Doc) =>
+            this.script.compiled &&
+            this.script.run( {
+                    this: doc, 
+                    //value: '',
+                    _setCacheResult_: this.setCacheResult,
+                    _last_: this._lastComputedResult,
+                    _readOnly_: true,
+                },
+                console.log
+            ).result
+        )(doc)
+    ); // prettier-ignore
 
-    [ToValue](doc: Doc)   { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) };  } // prettier-ignore
+    [ToValue](doc: Doc)   { if (ComputedField.useComputed) return { value: this.value(doc) };  } // prettier-ignore
     [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
 
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
-- 
cgit v1.2.3-70-g09d2


From 82a74549a6b1c942fabaf8396c450b70d65e81dd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Mar 2024 23:28:11 -0400
Subject: fixed following link to pdf text selection with highlights. fixed
 hide key/value in dashfieldView. fixed selecting dashFieldView nodes.

---
 src/client/documents/Documents.ts                  |  25 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |   1 +
 src/client/views/SidebarAnnos.tsx                  |   4 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           | 791 +++++----------------
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   4 +-
 .../views/nodes/RecordingBox/RecordingView.scss    | 322 ++++-----
 .../views/nodes/formattedText/DashFieldView.tsx    |  70 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  32 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   3 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |   1 +
 12 files changed, 445 insertions(+), 814 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b63c5e429..b3d14bc22 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
+import { OmitKeys, Utils } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { DocData, Initializing } from '../../fields/DocSymbols';
@@ -13,30 +14,29 @@ import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
-import { inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { SharingPermissions, inheritParentAcls } from '../../fields/util';
 import { Upload } from '../../server/SharedMediaTypes';
-import { OmitKeys, Utils } from '../../Utils';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { YoutubeBox } from '../apis/youtube/YoutubeBox';
 import { DragManager, dropActionType } from '../util/DragManager';
 import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoable, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionView } from '../views/collections/CollectionView';
+import { UndoManager, undoable } from '../util/UndoManager';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
 import { AudioBox, media_state } from '../views/nodes/AudioBox';
 import { ComparisonBox } from '../views/nodes/ComparisonBox';
 import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
+import { OpenWhere } from '../views/nodes/DocumentView';
 import { EquationBox } from '../views/nodes/EquationBox';
 import { FieldViewProps } from '../views/nodes/FieldView';
 import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
 import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
 import { ImageBox } from '../views/nodes/ImageBox';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
@@ -52,14 +52,14 @@ import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
 import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
 import { ScriptingBox } from '../views/nodes/ScriptingBox';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { PresBox } from '../views/nodes/trails/PresBox';
-import { PresElementBox } from '../views/nodes/trails/PresElementBox';
 import { VideoBox } from '../views/nodes/VideoBox';
 import { WebBox } from '../views/nodes/WebBox';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { PresBox } from '../views/nodes/trails/PresBox';
+import { PresElementBox } from '../views/nodes/trails/PresElementBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
-import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
-import { OpenWhere } from '../views/nodes/DocumentView';
 const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
@@ -204,6 +204,7 @@ export class DocumentOptions {
     overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false);
     overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', false);
     text?: RTFt = new RtfInfo('plain or rich text', true);
+    text_html?: STRt = new StrInfo('plain text or html', true);
     _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
     latitude?: NUMt = new NumInfo('latitude coordinate', false);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 11f6c82ec..f4599f04a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -321,9 +321,9 @@ export class CurrentUserUtils {
                         {type:"text",text:" "},
                         {type:"text",text:"Minerals in my tap water"},
                         {type:"text",text:"\n    \"Calcium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"","hideKey":false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:false,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Potassium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Magnesium\" : 10.01"}
                     ]}
                 ]},
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c29474fcd..0b68fd59e 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -89,6 +89,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
             annotationOn: this.props.Document,
             text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
+            text_html: this.props.selectionText() as any, 
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1e9272e93..3ad3c92da 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -94,7 +94,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 target[DocData][key] = val;
                 return {
                     type: 'dashField',
-                    attrs: { fieldKey: key, docId: '', hideKey: false, editable: true },
+                    attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
                     marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
                 };
             });
@@ -121,7 +121,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                             { type: 'pFontSize', attrs: { fontSize: '8px' } },
                             { type: 'em' },
                         ],
-                        attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, editable: false },
+                        attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, hideValue: false, editable: false },
                     },
                 ],
             },
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 6484658e0..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { aggregateBounds } from '../../../../Utils';
-import * as React from 'react';
 
 export interface ViewDefBounds {
     type: string;
@@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
+                backgroundColor: StrCast(layoutDoc.backgroundColor),
                 pair: { layout: doc },
                 replica: val.replicas[i],
             });
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 927e6fad4..36dad2747 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -6,7 +6,7 @@ import { IconButton, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
 import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
-import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { CirclePicker, ColorResult } from 'react-color';
@@ -14,7 +14,6 @@ import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils, Docs } from '../../../documents/Documents';
@@ -28,7 +27,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -53,7 +52,6 @@ import { MarkerIcons } from './MarkerIcons';
  * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
  */
 
-const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/  and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
 const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
 const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 
@@ -66,72 +64,69 @@ type PopupInfo = {
     description: string;
 };
 
-// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
-//     mapboxAccessToken: string;
-//     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
-//     position: ControlPosition;
-
-//     onResult: (...args: any[]) => void;
-// };
-
-type MapMarker = {
-    longitude: number;
-    latitude: number;
-};
-
-/**
- * Consider integrating later: allows for drawing, circling, making shapes on map
- */
-// const drawingManager = new window.google.maps.drawing.DrawingManager({
-//     drawingControl: true,
-//     drawingControlOptions: {
-//         position: google.maps.ControlPosition.TOP_RIGHT,
-//         drawingModes: [
-//             google.maps.drawing.OverlayType.MARKER,
-//             // currently we are not supporting the following drawing mode on map, a thought for future development
-//             google.maps.drawing.OverlayType.CIRCLE,
-//             google.maps.drawing.OverlayType.POLYLINE,
-//         ],
-//     },
-// });
-
 @observer
 export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBox, fieldKey);
     }
-    private _dragRef = React.createRef<HTMLDivElement>();
+    private _unmounting = false;
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _mapRef: React.RefObject<MapRef> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
-    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
-    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @computed get allSidebarDocs() {
-        return DocListCast(this.dataDoc[this.SidebarKey]);
-    }
+    @observable _featuresFromGeocodeResults: any[] = [];
+    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+    @observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected
+    @observable _mapReady = false;
+    @observable _isAnimating: boolean = false;
+    @observable _routeToAnimate: Doc | undefined = undefined;
+    @observable _animationPhase: number = 0;
+    @observable _finishedFlyTo: boolean = false;
+    @observable _frameId: number | null = null;
+    @observable _animationUtility: AnimationUtility | null = null;
+    @observable _settingsOpen: boolean = false;
+    @observable _mapStyle: string = 'mapbox://styles/mapbox/standard';
+    @observable _showTerrain: boolean = true;
+    @observable _currentPopup: PopupInfo | undefined = undefined;
+    @observable _isStreetViewAnimation: boolean = false;
+    @observable _animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+    @observable _animationLineColor: string = '#ffff00';
+    @observable _temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [] };
+    @observable _dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
+        type: 'Feature',
+        properties: {},
+        geometry: { type: 'LineString', coordinates: [] },
+    };
+
+    @observable path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
+        type: 'Feature',
+        geometry: { type: 'LineString', coordinates: [] },
+        properties: {},
+    };
+
     // this list contains pushpins and configs
-    @computed get allAnnotations() {
-        return DocListCast(this.dataDoc[this.annotationKey]);
-    }
-    @computed get allPushpins() {
-        return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
-    }
-    @computed get allRoutes() {
-        return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+    @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
+    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
+    @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
+    @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
+    @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
+    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } //prettier-ignore
+    @computed get sidebarColor() {
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
-        if (this.routeToAnimate?.routeCoordinates) {
-            const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+        if (this._routeToAnimate?.routeCoordinates) {
+            const originalCoordinates: Position[] = JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
             // const index = Math.floor(this.animationPhase * originalCoordinates.length);
-            const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
-            console.log('Animation phase', this.animationPhase);
+            const index = this._animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
+            console.log('Animation phase', this._animationPhase);
             const startIndex = Math.floor(index);
             const endIndex = Math.ceil(index);
             let feature: Feature<Geometry, GeoJsonProperties>;
@@ -147,7 +142,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 feature = {
                     type: 'Feature',
                     properties: {
-                        routeTitle: StrCast(this.routeToAnimate.title),
+                        routeTitle: StrCast(this._routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
@@ -158,9 +153,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 const fraction = index - startIndex;
 
                 const interpolator = d3.interpolateArray(startCoord, endCoord);
-
                 const interpolatedCoord = interpolator(fraction);
-
                 const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
 
                 geometry = {
@@ -170,14 +163,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 feature = {
                     type: 'Feature',
                     properties: {
-                        routeTitle: StrCast(this.routeToAnimate.title),
+                        routeTitle: StrCast(this._routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
             }
 
             autorun(() => {
-                const animationUtil = this.animationUtility;
+                const animationUtil = this._animationUtility;
                 const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
                 const newFeature: Feature<LineString, turf.Properties> = {
                     type: 'Feature',
@@ -204,11 +197,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         };
     }
     @computed get selectedRouteCoordinates(): Position[] {
-        let coordinates: Position[] = [];
-        if (this.routeToAnimate?.routeCoordinates) {
-            coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
-        }
-        return coordinates;
+        return !this._routeToAnimate?.routeCoordinates ? [] : JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
     }
 
     @computed get allRoutesGeoJson(): FeatureCollection {
@@ -233,29 +222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         };
     }
 
-    @computed get SidebarShown() {
-        return this.layoutDoc._layout_showSidebar ? true : false;
-    }
-    @computed get sidebarWidthPercent() {
-        return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
-    }
-    @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
-    }
-    @computed get SidebarKey() {
-        return this.fieldKey + '_sidebar';
-    }
-
     componentDidMount() {
         this._unmounting = false;
         this._props.setContentViewBox?.(this);
     }
 
-    _unmounting = false;
-    componentWillUnmount(): void {
+    componentWillUnmount() {
         this._unmounting = true;
         this.deselectPinOrRoute();
-        this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
         Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
     }
 
@@ -269,7 +243,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
         const docs = doc instanceof Doc ? [doc] : doc;
         docs.forEach(doc => {
-            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute;
+            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this._selectedPinOrRoute;
             if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
                 existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
             }
@@ -370,10 +344,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
         const sourceAnchorCreator = action(() => {
             const note = this.getAnchor(true);
-            if (note && this.selectedPinOrRoute) {
-                note.latitude = this.selectedPinOrRoute.latitude;
-                note.longitude = this.selectedPinOrRoute.longitude;
-                note.map = this.selectedPinOrRoute.map;
+            if (note && this._selectedPinOrRoute) {
+                note.latitude = this._selectedPinOrRoute.latitude;
+                note.longitude = this._selectedPinOrRoute.longitude;
+                note.map = this._selectedPinOrRoute.map;
             }
             return note as Doc;
         });
@@ -399,10 +373,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const createFunc = undoable(
             action(() => {
                 const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
-                if (note && this.selectedPinOrRoute) {
-                    note.latitude = this.selectedPinOrRoute.latitude;
-                    note.longitude = this.selectedPinOrRoute.longitude;
-                    note.map = this.selectedPinOrRoute.map;
+                if (note && this._selectedPinOrRoute) {
+                    note.latitude = this._selectedPinOrRoute.latitude;
+                    note.longitude = this._selectedPinOrRoute.longitude;
+                    note.map = this._selectedPinOrRoute.map;
                 }
             }),
             'create note annotation'
@@ -423,12 +397,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return false;
     };
 
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
-
-    addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
-
     pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
-
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
@@ -439,52 +408,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     savedAnnotations = () => this._savedAnnotations;
 
-    _bingSearchManager: any;
-    _bingMap: any;
-    get MicrosoftMaps() {
-        return (window as any).Microsoft.Maps;
-    }
-    // uses Bing Search to retrieve lat/lng for a location.  eg.,
-    //   const results = this.geocodeQuery(map.map, 'Philadelphia, PA');
-    // to move the map to that location:
-    //     const location = await this.geocodeQuery(this._bingMap, 'Philadelphia, PA');
-    //     this._bingMap.current.setView({
-    //        mapTypeId: this.MicrosoftMaps.MapTypeId.aerial,
-    //        center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
-    //     });
-    //
-    bingGeocode = (map: any, query: string) => {
-        return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
-            //If search manager is not defined, load the search module.
-            if (!this._bingSearchManager) {
-                //Create an instance of the search manager and call the geocodeQuery function again.
-                this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
-                    this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
-                    res(this.bingGeocode(map, query));
-                });
-            } else {
-                this._bingSearchManager.geocode({
-                    where: query,
-                    callback: action((r: any) => res(r.results[0].location)),
-                    errorCallback: (e: any) => reject(),
-                });
-            }
-        });
-    };
-
-    @observable
-    bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
-
-    geoDataRequestOptions = {
-        entityType: 'PopulatedPlace',
-    };
-
-    // The pin that is selected
-    @observable selectedPinOrRoute: Doc | undefined = undefined;
-
     @action
     deselectPinOrRoute = () => {
-        if (this.selectedPinOrRoute) {
+        if (this._selectedPinOrRoute) {
             // // Removes filter
             // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
             // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
@@ -511,79 +437,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
-    /*
-     * Pushpin onclick
-     */
-    @action
-    pushpinClicked = (pinDoc: Doc) => {
-        this.deselectPinOrRoute();
-        this.selectedPinOrRoute = pinDoc;
-        this.bingSearchBarContents = pinDoc.map;
-
-        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
-        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
-
-        this.recolorPin(this.selectedPinOrRoute, 'green');
-
-        MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
-        MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
-        MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
-        MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
-
-        const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
-        const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
-        const y = point.y + this._props.PanelHeight() / 2 + 32;
-        const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
-        MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
-
-        document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
-    };
-
-    /**
-     * Map OnClick
-     */
-    @action
-    mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
-        this._props.select(false);
-        this.deselectPinOrRoute();
-    };
-    /*
-     * Updates values of layout doc to match the current map
-     */
-    @action
-    mapRecentered = () => {
-        if (
-            Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || //
-            Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7
-        ) {
-            this.dataDoc.latitude = this._bingMap.current.getCenter().latitude;
-            this.dataDoc.longitude = this._bingMap.current.getCenter().longitude;
-            this.dataDoc.map = '';
-            this.bingSearchBarContents = '';
-        }
-        this.dataDoc.map_zoom = this._bingMap.current.getZoom();
-    };
-    /*
-     * Updates maptype
-     */
-    @action
-    updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
-
-    /*
-     * For Bing Maps
-     * Called by search button's onClick
-     * Finds the geocode of the searched contents and sets location to that location
-     **/
-    @action
-    bingSearch = () => {
-        return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
-            this.dataDoc.latitude = location.latitude;
-            this.dataDoc.longitude = location.longitude;
-            this.dataDoc.map_zoom = this._bingMap.current.getZoom();
-            this.dataDoc.map = this.bingSearchBarContents;
-        });
-    };
 
     /*
      * Returns doc w/ relevant info
@@ -592,14 +445,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
-            config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
-            config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
+            text: (StrCast(this._selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
+            config_latitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
+            config_longitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
             // config_map_type: StrCast(this.dataDoc.map_type),
-            config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
+            config_map: StrCast((existingPin ?? this._selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
-            mapPin: existingPin ?? this.selectedPinOrRoute,
+            mapPin: existingPin ?? this._selectedPinOrRoute,
             annotationOn: this.Document,
         });
         if (anchor) {
@@ -613,25 +466,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     map_docToPinMap = new Map<Doc, any>();
     map_pinHighlighted = new Map<Doc, boolean>();
-    /*
-     *   Input: pin doc
-     *   Adds MicrosoftMaps Pushpin to the map (render)
-     */
-    @action
-    addPushpin = (pin: Doc) => {
-        const pushPin = pin.infoWindowOpen
-            ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {})
-            : new this.MicrosoftMaps.Pushpin(
-                  new this.MicrosoftMaps.Location(pin.latitude, pin.longitude)
-                  // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'}
-              );
-
-        this._bingMap.current.entities.push(pushPin);
-
-        this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
-        // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
-        this.map_docToPinMap.set(pin, pushPin);
-    };
 
     /*
      *   Input: pin doc
@@ -640,27 +474,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     @action
     removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);
 
-    /*
-     * Removes pushpin from map render
-     */
-    deletePushpin = (pinDoc: Doc) => {
-        if (!this._unmounting) {
-            this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
-        }
-        this.map_docToPinMap.delete(pinDoc);
-        this.selectedPinOrRoute = undefined;
-    };
-
     @action
     deleteSelectedPinOrRoute = undoable(() => {
         console.log('deleting');
-        if (this.selectedPinOrRoute) {
+        if (this._selectedPinOrRoute) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', this._selectedPinOrRoute.latitude, 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', this._selectedPinOrRoute.longitude, 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove');
 
-            this.removePushpinOrRoute(this.selectedPinOrRoute);
+            this.removePushpinOrRoute(this._selectedPinOrRoute);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -677,7 +500,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         e.preventDefault();
         MapAnchorMenu.Instance.fadeOut(true);
         runInAction(() => {
-            this.temporaryRouteSource = {
+            this._temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
             };
@@ -688,9 +511,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     centerOnSelectedPin = () => {
-        if (this.selectedPinOrRoute) {
+        if (this._selectedPinOrRoute) {
             this._mapRef.current?.flyTo({
-                center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)],
+                center: [NumCast(this._selectedPinOrRoute.longitude), NumCast(this._selectedPinOrRoute.latitude)],
             });
         }
         // if (this.selectedPin) {
@@ -703,33 +526,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
     };
 
-    /**
-     * View options for bing maps
-     */
-    bingViewOptions = {
-        // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng },
-        zoom: this.dataDoc.latitude ?? 10,
-        mapTypeId: 'grayscale',
-    };
-
-    /**
-     * Map options
-     */
-    bingMapOptions = {
-        navigationBarMode: 'square',
-        backgroundColor: '#f1f3f4',
-        enableInertia: true,
-        supportedMapTypes: ['grayscale', 'canvasLight'],
-        disableMapTypeSelectorMouseOver: true,
-        // showScalebar:true
-        // disableRoadView:true,
-        // disableBirdseye:true
-        streetsideOptions: {
-            showProblemReporting: false,
-            showCurrentAddress: false,
-        },
-    };
-
     recolorPin = (pin: Doc, color?: string) => {
         // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
         // this.map_docToPinMap.delete(pin);
@@ -739,109 +535,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // this.map_docToPinMap.set(pin, newpin);
     };
 
-    /*
-     * Called when BingMap is first rendered
-     * Initializes starting values
-     */
-    @observable _mapReady = false;
-    @action
-    bingMapReady = (map: any) => {
-        this._mapReady = true;
-        this._bingMap = map.map;
-        if (!this._bingMap.current) {
-            alert('NO Map!?');
-        }
-        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick);
-        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change'));
-        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
-
-        this._disposers.mapLocation = reaction(
-            () => this.Document.map,
-            mapLoc => (this.bingSearchBarContents = mapLoc),
-            { fireImmediately: true }
-        );
-        this._disposers.highlight = reaction(
-            () => this.allAnnotations.map(doc => doc[Highlight]),
-            () => {
-                const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
-                allConfigPins.forEach(({ doc, pushpin }) => {
-                    if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
-                        this.recolorPin(pushpin);
-                        this.map_pinHighlighted.delete(pushpin);
-                    }
-                });
-                allConfigPins.forEach(({ doc, pushpin }) => {
-                    if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) {
-                        this.recolorPin(pushpin, 'orange');
-                        this.map_pinHighlighted.set(pushpin, true);
-                    }
-                });
-            },
-            { fireImmediately: true }
-        );
-
-        this._disposers.location = reaction(
-            () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
-            locationObject => {
-                // if (this._bingMap.current)
-                try {
-                    locationObject?.zoom &&
-                        this._bingMap.current?.setView({
-                            mapTypeId: locationObject.mapType,
-                            zoom: locationObject.zoom,
-                            center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng),
-                        });
-                } catch (e) {
-                    console.log(e);
-                }
-            },
-            { fireImmediately: true }
-        );
-    };
-
-    dragToggle = (e: React.PointerEvent) => {
-        let dragClone: HTMLDivElement | undefined;
-
-        setupMoveUpEvents(
-            e,
-            e,
-            e => {
-                // move event
-                if (!dragClone) {
-                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin
-                    dragClone.style.position = 'absolute';
-                    dragClone.style.zIndex = '10000';
-                    DragManager.Root().appendChild(dragClone); // add clone to root
-                }
-                dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
-                return false;
-            },
-            e => {
-                // up event
-                if (!dragClone) return;
-                DragManager.Root().removeChild(dragClone);
-                let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
-                while (target) {
-                    if (target === this._ref.current) {
-                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
-                        const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
-                        const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
-                        const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
-                        this.createPushpin(location.latitude, location.longitude);
-                        break;
-                    }
-                    target = target.parentElement;
-                }
-            },
-            e => {
-                const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
-                if (this.bingSearchBarContents) {
-                    this.bingSearch().then(createPin);
-                } else createPin();
-            }
-        );
-    };
-
     // incrementer: number = 0;
     /*
      * Creates Pushpin doc and adds it to the list of annotations
@@ -880,7 +573,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             if (createPinForDestination) {
                 this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
             }
-            this.temporaryRouteSource = {
+            this._temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
             };
@@ -890,10 +583,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // TODO: Display error that can't create route to same location
     }, 'createmaproute');
 
-    searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
-
-    @observable
-    featuresFromGeocodeResults: any[] = [];
+    @action
+    searchbarKeyDown = (e: any) => {
+        if (e.key === 'Enter' && this._featuresFromGeocodeResults) {
+            const center = this._featuresFromGeocodeResults[0]?.center;
+            this._featuresFromGeocodeResults = [];
+            setTimeout(() => center && this._mapRef.current?.flyTo({ center }));
+        }
+    };
 
     @action
     addMarkerForFeature = (feature: any) => {
@@ -910,7 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     center: feature.center,
                 });
             }
-            this.featuresFromGeocodeResults = [];
+            this._featuresFromGeocodeResults = [];
         } else {
             // TODO: handle error
         }
@@ -922,11 +619,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
      */
     handleSearchChange = async (searchText: string) => {
         const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
-        if (features && !this.isAnimating) {
+        if (features && !this._isAnimating) {
             runInAction(() => {
-                this.settingsOpen = false;
-                this.featuresFromGeocodeResults = features;
-                this.routeToAnimate = undefined;
+                this._settingsOpen = false;
+                this._featuresFromGeocodeResults = features;
+                this._routeToAnimate = undefined;
             });
         }
         // try {
@@ -946,8 +643,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     handleMapClick = (e: MapLayerMouseEvent) => {
-        this.featuresFromGeocodeResults = [];
-        this.settingsOpen = false;
+        this._featuresFromGeocodeResults = [];
+        this._settingsOpen = false;
         if (this._mapRef.current) {
             const features = this._mapRef.current.queryRenderedFeatures(e.point, {
                 layers: ['map-routes-layer'],
@@ -960,8 +657,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
                 this.deselectPinOrRoute(); // TODO: Also deselect route if selected
                 if (routeDoc) {
-                    this.selectedPinOrRoute = routeDoc;
-                    Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+                    this._selectedPinOrRoute = routeDoc;
+                    Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
 
                     // TODO: Recolor route
 
@@ -1008,7 +705,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
         if (features) {
             runInAction(() => {
-                this.featuresFromGeocodeResults = features;
+                this._featuresFromGeocodeResults = features;
             });
         }
 
@@ -1028,21 +725,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // }
     };
 
-    @observable
-    currentPopup: PopupInfo | undefined = undefined;
-
     @action
     handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
-        this.featuresFromGeocodeResults = [];
+        this._featuresFromGeocodeResults = [];
         this.deselectPinOrRoute(); // TODO: check this method
-        this.selectedPinOrRoute = pinDoc;
+        this._selectedPinOrRoute = pinDoc;
         // this.bingSearchBarContents = pinDoc.map;
 
         // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
         // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
 
-        this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
+        this.recolorPin(this._selectedPinOrRoute, 'green'); // TODO: check this method
 
         MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
         MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
@@ -1072,12 +766,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // })
     };
 
-    @observable
-    temporaryRouteSource: FeatureCollection = {
-        type: 'FeatureCollection',
-        features: [],
-    };
-
     @action
     displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
         if (routeInfoMap) {
@@ -1096,41 +784,23 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             };
             // TODO: Create pin for destination
             // TODO: Fly to point where full route will be shown
-            this.temporaryRouteSource = newTempRouteSource;
+            this._temporaryRouteSource = newTempRouteSource;
         }
     };
 
-    @observable
-    isAnimating: boolean = false;
-
-    @observable
-    routeToAnimate: Doc | undefined = undefined;
-
-    @observable
-    animationPhase: number = 0;
-
-    @observable
-    finishedFlyTo: boolean = false;
-
     @action
     setAnimationPhase = (newValue: number) => {
-        this.animationPhase = newValue;
+        this._animationPhase = newValue;
     };
 
-    @observable
-    frameId: number | null = null;
-
     @action
     setFrameId = (frameId: number) => {
-        this.frameId = frameId;
+        this._frameId = frameId;
     };
 
-    @observable
-    animationUtility: AnimationUtility | null = null;
-
     @action
     setAnimationUtility = (util: AnimationUtility) => {
-        this.animationUtility = util;
+        this._animationUtility = util;
     };
 
     @action
@@ -1138,8 +808,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (routeDoc) {
             MapAnchorMenu.Instance.fadeOut(true);
             document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
-            this.featuresFromGeocodeResults = [];
-            this.routeToAnimate = routeDoc;
+            this._featuresFromGeocodeResults = [];
+            this._routeToAnimate = routeDoc;
         }
     };
 
@@ -1161,29 +831,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @computed
     get preAnimationViewState() {
-        if (!this.isAnimating) {
+        if (!this._isAnimating) {
             return this.mapboxMapViewState;
         }
     }
 
-    @observable
-    isStreetViewAnimation: boolean = false;
-
-    @observable
-    animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
-
-    @observable
-    animationLineColor: string = '#ffff00';
-
     @action
     setAnimationLineColor = (color: ColorResult) => {
-        this.animationLineColor = color.hex;
+        this._animationLineColor = color.hex;
     };
 
     @action
     updateAnimationSpeed = () => {
         let newAnimationSpeed: AnimationSpeed;
-        switch (this.animationSpeed) {
+        switch (this._animationSpeed) {
             case AnimationSpeed.SLOW:
                 newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
@@ -1197,63 +858,33 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
         }
-        this.animationSpeed = newAnimationSpeed;
-        if (this.animationUtility) {
-            this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
+        this._animationSpeed = newAnimationSpeed;
+        if (this._animationUtility) {
+            this._animationUtility.updateAnimationSpeed(newAnimationSpeed);
         }
     };
     @computed get animationSpeedTooltipText(): string {
-        switch (this.animationSpeed) {
-            case AnimationSpeed.SLOW:
-                return '1x speed';
-            case AnimationSpeed.MEDIUM:
-                return '2x speed';
-            case AnimationSpeed.FAST:
-                return '3x speed';
-            default:
-                return '2x speed';
-        }
+        switch (this._animationSpeed) {
+            case AnimationSpeed.SLOW:   return '1x speed';
+            case AnimationSpeed.MEDIUM: return '2x speed';
+            case AnimationSpeed.FAST:   return '3x speed';
+            default:                    return '2x speed';
+        } // prettier-ignore
     }
     @computed get animationSpeedIcon(): JSX.Element {
-        switch (this.animationSpeed) {
-            case AnimationSpeed.SLOW:
-                return slowSpeedIcon;
-            case AnimationSpeed.MEDIUM:
-                return mediumSpeedIcon;
-            case AnimationSpeed.FAST:
-                return fastSpeedIcon;
-            default:
-                return mediumSpeedIcon;
-        }
+        switch (this._animationSpeed) {
+            case AnimationSpeed.SLOW:   return slowSpeedIcon;
+            case AnimationSpeed.MEDIUM: return mediumSpeedIcon;
+            case AnimationSpeed.FAST:   return fastSpeedIcon;
+            default:                    return mediumSpeedIcon;
+        } // prettier-ignore
     }
 
     @action
     toggleIsStreetViewAnimation = () => {
-        const newVal = !this.isStreetViewAnimation;
-        this.isStreetViewAnimation = newVal;
-        if (this.animationUtility) {
-            this.animationUtility.updateIsStreetViewAnimation(newVal);
-        }
-    };
-
-    @observable
-    dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
-        type: 'Feature',
-        properties: {},
-        geometry: {
-            type: 'LineString',
-            coordinates: [],
-        },
-    };
-
-    @observable
-    path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
-        type: 'Feature',
-        geometry: {
-            type: 'LineString',
-            coordinates: [],
-        },
-        properties: {},
+        const newVal = !this._isStreetViewAnimation;
+        this._isStreetViewAnimation = newVal;
+        this._animationUtility?.updateIsStreetViewAnimation(newVal);
     };
 
     getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
@@ -1272,19 +903,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     playAnimation = (status: AnimationStatus) => {
-        if (!this._mapRef.current || !this.routeToAnimate) {
+        if (!this._mapRef.current || !this._routeToAnimate) {
             return;
         }
 
-        this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
-        this.frameId = AnimationStatus.RESUME ? this.frameId : null;
-        this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
+        this._animationPhase = status === AnimationStatus.RESUME ? this._animationPhase : 0;
+        this._frameId = AnimationStatus.RESUME ? this._frameId : null;
+        this._finishedFlyTo = AnimationStatus.RESUME ? this._finishedFlyTo : false;
 
         const path = turf.lineString(this.selectedRouteCoordinates);
 
-        this.settingsOpen = false;
+        this._settingsOpen = false;
         this.path = path;
-        this.isAnimating = true;
+        this._isAnimating = true;
 
         runInAction(() => {
             return new Promise<void>(async resolve => {
@@ -1293,18 +924,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     lat: this.selectedRouteCoordinates[0][1],
                 };
 
-                const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current);
-                runInAction(() => {
-                    this.setAnimationUtility(animationUtil);
-                });
-
-                const updateFrameId = (newFrameId: number) => {
-                    this.setFrameId(newFrameId);
-                };
+                const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current);
+                runInAction(() => this.setAnimationUtility(animationUtil));
 
-                const updateAnimationPhase = (newAnimationPhase: number) => {
-                    this.setAnimationPhase(newAnimationPhase);
-                };
+                const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId);
+                const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);
 
                 if (status !== AnimationStatus.RESUME) {
                     const result = await animationUtil.flyInAndRotate({
@@ -1324,9 +948,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     console.log('Altitude: ', result.altitude);
                 }
 
-                runInAction(() => {
-                    this.finishedFlyTo = true;
-                });
+                runInAction(() => (this._finishedFlyTo = true));
 
                 // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
                 await animationUtil.animatePath({
@@ -1335,7 +957,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     // startBearing: -20,
                     // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
                     // pitch: this.isStreetViewAnimation ? 80: 50,
-                    currentAnimationPhase: this.animationPhase,
+                    currentAnimationPhase: this._animationPhase,
                     updateAnimationPhase,
                     updateFrameId,
                 });
@@ -1353,7 +975,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 });
 
                 setTimeout(() => {
-                    this.isStreetViewAnimation = false;
+                    this._isStreetViewAnimation = false;
                     resolve();
                 }, 10000);
             });
@@ -1362,27 +984,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     pauseAnimation = () => {
-        if (this.frameId && this.animationPhase > 0) {
-            window.cancelAnimationFrame(this.frameId);
-            this.frameId = null;
-            this.isAnimating = false;
+        if (this._frameId && this._animationPhase > 0) {
+            window.cancelAnimationFrame(this._frameId);
+            this._frameId = null;
+            this._isAnimating = false;
         }
     };
 
     @action
     stopAnimation = (close: boolean) => {
-        if (this.frameId) {
-            window.cancelAnimationFrame(this.frameId);
+        if (this._frameId) {
+            window.cancelAnimationFrame(this._frameId);
         }
-        this.animationPhase = 0;
-        this.frameId = null;
-        this.finishedFlyTo = false;
-        this.isAnimating = false;
+        this._animationPhase = 0;
+        this._frameId = null;
+        this._finishedFlyTo = false;
+        this._isAnimating = false;
         if (close) {
-            this.animationSpeed = AnimationSpeed.MEDIUM;
-            this.isStreetViewAnimation = false;
-            this.routeToAnimate = undefined;
-            this.animationUtility = null;
+            this._animationSpeed = AnimationSpeed.MEDIUM;
+            this._isStreetViewAnimation = false;
+            this._routeToAnimate = undefined;
+            this._animationUtility = null;
         }
     };
 
@@ -1390,21 +1012,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return (
             <>
                 <IconButton
-                    tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+                    tooltip={this._isAnimating && this._finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
                     onPointerDown={() => {
-                        if (this.isAnimating && this.finishedFlyTo) {
+                        if (this._isAnimating && this._finishedFlyTo) {
                             this.pauseAnimation();
-                        } else if (this.animationPhase > 0) {
+                        } else if (this._animationPhase > 0) {
                             this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase
                         } else {
                             this.playAnimation(AnimationStatus.START); // Play from the beginning
                         }
                     }}
-                    icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+                    icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
                     color="black"
                     size={Size.MEDIUM}
                 />
-                {this.isAnimating && this.finishedFlyTo && (
+                {this._isAnimating && this._finishedFlyTo && (
                     <IconButton
                         tooltip="Restart animation"
                         onPointerDown={() => {
@@ -1420,7 +1042,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 <>
                     <div className="animation-suboptions">
                         <div>|</div>
-                        <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
+                        <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this._isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
                         <div id="divider">|</div>
                         <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
                         <div id="divider">|</div>
@@ -1436,26 +1058,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     hideRoute = () => {
-        this.temporaryRouteSource = {
+        this._temporaryRouteSource = {
             type: 'FeatureCollection',
             features: [],
         };
     };
 
-    @observable
-    settingsOpen: boolean = false;
-
-    @observable
-    mapStyle: string = 'mapbox://styles/mapbox/standard';
-
-    @observable
-    showTerrain: boolean = true;
-
     @action
     toggleSettings = () => {
-        if (!this.isAnimating && this.animationPhase == 0) {
-            this.featuresFromGeocodeResults = [];
-            this.settingsOpen = !this.settingsOpen;
+        if (!this._isAnimating && this._animationPhase == 0) {
+            this._featuresFromGeocodeResults = [];
+            this._settingsOpen = !this._settingsOpen;
         }
     };
 
@@ -1500,14 +1113,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     @action
     onStepZoomChange = (increment: boolean) => {
         if (this._mapRef.current) {
-            let newZoom: number;
-            if (increment) {
-                console.log('inc');
-                newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1);
-            } else {
-                console.log('dec');
-                newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1);
-            }
+            const newZoom = increment //
+                ? Math.min(16, this.mapboxMapViewState.zoom + 1)
+                : Math.max(0, this.mapboxMapViewState.zoom - 1);
+
             this._mapRef.current.setZoom(newZoom);
             this.dataDoc.map_zoom = newZoom;
         }
@@ -1523,7 +1132,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
 
     @action
-    toggleShowTerrain = () => (this.showTerrain = !this.showTerrain);
+    toggleShowTerrain = () => (this._showTerrain = !this._showTerrain);
 
     getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
         const markerType = StrCast(pinDoc.markerType);
@@ -1532,25 +1141,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
     };
 
-    static _firstRender = true;
-    static _rerenderDelay = 500;
-    _rerenderTimeout: any;
+    _textRef = React.createRef<any>();
     render() {
-        // bcz:  no idea what's going on here, but bings maps have some kind of bug
-        // such that we need to delay rendering a second map on startup until the first map is rendered.
-        this.Document[DocCss];
-        if (MapBox._rerenderDelay) {
-            // prettier-ignore
-            this._rerenderTimeout = this._rerenderTimeout ??
-                setTimeout(action(() => {
-                    if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
-                        MapBox._rerenderDelay = 0;
-                    }
-                    this._rerenderTimeout = undefined;
-                    this.Document[DocCss] = this.Document[DocCss] + 1;
-                }), MapBox._rerenderDelay);
-            return null;
-        }
         const scale = this._props.NativeDimScaling?.() || 1;
         const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
 
@@ -1560,20 +1152,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 <div
                     className="mapBox-wrapper"
                     onWheel={e => e.stopPropagation()}
-                    onPointerDown={async e => {
-                        e.button === 0 && !e.ctrlKey && e.stopPropagation();
-                    }}
+                    onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
                     style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.IsDragging ? null : renderAnnotations()}
-                    {!this.routeToAnimate && (
+                    {!this._routeToAnimate && (
                         <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
-                            <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
+                            <TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={(e: any) => this.handleSearchChange(e.target.value)} />
                             <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
                         </div>
                     )}
-                    {this.settingsOpen && !this.routeToAnimate && (
+                    {this._settingsOpen && !this._routeToAnimate && (
                         <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
                             <div className="mapbox-style-select">
                                 <div>Map Style:</div>
@@ -1605,21 +1195,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             </div>
                             <div className="mapbox-terrain-selection">
                                 <div>Show terrain: </div>
-                                <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} />
+                                <input type="checkbox" checked={this._showTerrain} onChange={this.toggleShowTerrain} />
                             </div>
                         </div>
                     )}
-                    {this.routeToAnimate && (
+                    {this._routeToAnimate && (
                         <div className="animation-panel" style={{ width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)` }}>
-                            <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div>
+                            <div id="route-to-animate-title">{StrCast(this._routeToAnimate.title)}</div>
                             <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
                         </div>
                     )}
-                    {this.featuresFromGeocodeResults.length > 0 && (
+                    {this._featuresFromGeocodeResults.length > 0 && (
                         <div className="mapbox-geocoding-search-results">
-                            <React.Fragment>
+                            <>
                                 <h4>Choose a location for your pin: </h4>
-                                {this.featuresFromGeocodeResults
+                                {this._featuresFromGeocodeResults
                                     .filter(feature => feature.place_name)
                                     .map((feature, idx) => (
                                         <div
@@ -1632,14 +1222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                             <div className="search-result-place-name">{feature.place_name}</div>
                                         </div>
                                     ))}
-                            </React.Fragment>
+                            </>
                         </div>
                     )}
                     <MapProvider>
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            viewState={this.isAnimating || this.routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+                            viewState={this._isAnimating || this._routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
                                 position: 'absolute',
@@ -1649,20 +1239,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                 width: NumCast(this.layoutDoc._width) * parscale,
                                 height: NumCast(this.layoutDoc._height) * parscale,
                             }}
-                            initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+                            initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState}
                             onZoom={this.onMapZoom}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
                             onDblClick={this.handleMapDblClick}
-                            terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+                            terrain={this._showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
                             <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
-                            <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} />
+                            <Source id="temporary-route" type="geojson" data={this._temporaryRouteSource} />
                             <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
                             <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
-                            {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />}
-                            {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && (
+                            {!this._isAnimating && this._animationPhase == 0 && (
+                                <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />
+                            )}
+                            {this._routeToAnimate && (this._isAnimating || this._animationPhase > 0) && (
                                 <>
-                                    {!this.isStreetViewAnimation && (
+                                    {!this._isStreetViewAnimation && (
                                         <>
                                             <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
                                             <Layer
@@ -1670,7 +1262,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                                 type="line"
                                                 source="animated-route"
                                                 paint={{
-                                                    'line-color': this.animationLineColor,
+                                                    'line-color': this._animationLineColor,
                                                     'line-width': 5,
                                                 }}
                                             />
@@ -1722,10 +1314,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             )}
 
                             <>
-                                {!this.isAnimating &&
-                                    this.animationPhase == 0 &&
-                                    this.allPushpins
-                                        // .filter(anno => !anno.layout_unrendered)
+                                {!this._isAnimating &&
+                                    this._animationPhase == 0 &&
+                                    this.allPushpins // .filter(anno => !anno.layout_unrendered)
                                         .map((pushpin, idx) => (
                                             <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
                                                 {this.getMarkerIcon(pushpin)}
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index fc5b4dd18..8ebc90157 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
-import { MapBox } from './MapBox';
+import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer';
 
 /**
  * Map Pushpin doc class
@@ -18,7 +18,7 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     get mapBoxView() {
-        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
+        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBoxContainer;
     }
     get mapBox() {
         return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
index 287cccd8f..f2d5a980d 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.scss
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -1,208 +1,200 @@
 video {
-      // flex: 100%;
-      width: 100%;
-      // min-height: 400px;
-      //height: auto;
-      height: 100%;
-      //display: block;
-      object-fit: cover;
-      background-color: black;
-}
-
-button {
-      margin: 0 .5rem
+    // flex: 100%;
+    width: 100%;
+    // min-height: 400px;
+    //height: auto;
+    height: 100%;
+    //display: block;
+    object-fit: cover;
+    background-color: black;
 }
 
 .recording-container {
-      height: 100%;
-      width: 100%;
-      // display: flex;
-      pointer-events: all;
-      background-color: black;
+    height: 100%;
+    width: 100%;
+    // display: flex;
+    pointer-events: all;
+    background-color: black;
+    button {
+        margin: 0 0.5rem;
+    }
 }
 
 .video-wrapper {
-      //   max-width: 600px;
-      //   max-width: 700px;
-      // position: relative;
-      display: flex;
-      justify-content: center;
-      //   overflow: hidden;
-      border-radius: 10px;
-      margin: 0;
+    //   max-width: 600px;
+    //   max-width: 700px;
+    // position: relative;
+    display: flex;
+    justify-content: center;
+    //   overflow: hidden;
+    border-radius: 10px;
+    margin: 0;
 }
 
 .video-wrapper:hover .controls {
-      bottom: 34.5px;
-      transform: translateY(0%);
-      opacity: 100%;
+    bottom: 34.5px;
+    transform: translateY(0%);
+    opacity: 100%;
 }
 
 .controls {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      position: absolute;
-      width: 100%;
-      flex-wrap: wrap;
-      background: rgba(255, 255, 255, 0.25);
-      box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
-      // backdrop-filter: blur(4px);
-      border-radius: 10px;
-      border: 1px solid rgba(255, 255, 255, 0.18);
-      transition: all 0.3s ease-in-out;
-      bottom: 34.5px;
-      height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: absolute;
+    width: 100%;
+    flex-wrap: wrap;
+    background: rgba(255, 255, 255, 0.25);
+    box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
+    // backdrop-filter: blur(4px);
+    border-radius: 10px;
+    border: 1px solid rgba(255, 255, 255, 0.18);
+    transition: all 0.3s ease-in-out;
+    bottom: 34.5px;
+    height: 60px;
 }
 
 .controls:active {
-      bottom: 40px;
+    bottom: 40px;
 }
 
 .actions button {
-      background: none;
-      border: none;
-      outline: none;
-      cursor: pointer;
+    background: none;
+    border: none;
+    outline: none;
+    cursor: pointer;
 }
 
 .actions button i {
-      background-color: none;
-      color: white;
-      font-size: 30px;
+    background-color: none;
+    color: white;
+    font-size: 30px;
 }
 
-
 .velocity {
-      appearance: none;
-      background: none;
-      color: white;
-      outline: none;
-      border: none;
-      text-align: center;
-      font-size: 16px;
+    appearance: none;
+    background: none;
+    color: white;
+    outline: none;
+    border: none;
+    text-align: center;
+    font-size: 16px;
 }
 
 .mute-btn {
-      background: none;
-      border: none;
-      outline: none;
-      cursor: pointer;
+    background: none;
+    border: none;
+    outline: none;
+    cursor: pointer;
 }
 
 .mute-btn i {
-      background-color: none;
-      color: white;
-      font-size: 20px;
+    background-color: none;
+    color: white;
+    font-size: 20px;
 }
 
 .recording-sign {
-      height: 20px;
-      width: auto;
-      display: flex;
-      flex-direction: row;
-      position: absolute;
-      top: 10px;
-      right: 15px;
-      align-items: center;
-      justify-content: center;
-
-      .timer {
-            font-size: 15px;
-            color: white;
-            margin: 0;
-      }
-
-      .dot {
-            height: 15px;
-            width: 15px;
-            margin: 5px;
-            background-color: red;
-            border-radius: 50%;
-            display: inline-block;
-      }
+    height: 20px;
+    width: auto;
+    display: flex;
+    flex-direction: row;
+    position: absolute;
+    top: 10px;
+    right: 15px;
+    align-items: center;
+    justify-content: center;
+
+    .timer {
+        font-size: 15px;
+        color: white;
+        margin: 0;
+    }
+
+    .dot {
+        height: 15px;
+        width: 15px;
+        margin: 5px;
+        background-color: red;
+        border-radius: 50%;
+        display: inline-block;
+    }
 }
 
 .controls-inner-container {
-      display: flex;
-      flex-direction: row;
-      position: relative;
-      width: 100%;
-      align-items: center;
-      justify-content: center;
+    display: flex;
+    flex-direction: row;
+    position: relative;
+    width: 100%;
+    align-items: center;
+    justify-content: center;
 }
 
 .record-button-wrapper {
-      width: 35px;
-      height: 35px;
-      font-size: 0;
-      background-color: grey;
-      border: 0px;
-      border-radius: 35px;
-      margin: 10px;
-      display: flex;
-      justify-content: center;
-
-      .record-button {
-            background-color: red;
-            border: 0px;
-            border-radius: 50%;
-            height: 80%;
-            width: 80%;
-            align-self: center;
-            margin: 0;
-
-            &:hover {
-                  height: 85%;
-                  width: 85%;
-            }
-      }
-
-      .stop-button {
-            background-color: red;
-            border: 0px;
-            border-radius: 10%;
-            height: 70%;
-            width: 70%;
-            align-self: center;
-            margin: 0;
-
-
-            // &:hover {
-            //   width: 40px;
-            //   height: 40px
-            // }
-      }
-
+    width: 35px;
+    height: 35px;
+    font-size: 0;
+    background-color: grey;
+    border: 0px;
+    border-radius: 35px;
+    margin: 10px;
+    display: flex;
+    justify-content: center;
+
+    .record-button {
+        background-color: red;
+        border: 0px;
+        border-radius: 50%;
+        height: 80%;
+        width: 80%;
+        align-self: center;
+        margin: 0;
+
+        &:hover {
+            height: 85%;
+            width: 85%;
+        }
+    }
+
+    .stop-button {
+        background-color: red;
+        border: 0px;
+        border-radius: 10%;
+        height: 70%;
+        width: 70%;
+        align-self: center;
+        margin: 0;
+
+        // &:hover {
+        //   width: 40px;
+        //   height: 40px
+        // }
+    }
 }
 
 .options-wrapper {
-      height: 100%;
-      display: flex;
-      flex-direction: row;
-      align-content: center;
-      position: relative;
-      top: 0;
-      bottom: 0;
-
-      &.video-edit-wrapper {
-
-            // right: 50% - 15;
-
-            .track-screen {
-                  font-weight: 200;
-            }
-
-      }
-
-      &.track-screen-wrapper {
-
-            // right: 50% - 30;
-
-            .track-screen {
-                  font-weight: 200;
-                  color: aqua;
-            }
-
-      }
-}
\ No newline at end of file
+    height: 100%;
+    display: flex;
+    flex-direction: row;
+    align-content: center;
+    position: relative;
+    top: 0;
+    bottom: 0;
+
+    &.video-edit-wrapper {
+        // right: 50% - 15;
+
+        .track-screen {
+            font-weight: 200;
+        }
+    }
+
+    &.track-screen-wrapper {
+        // right: 50% - 30;
+
+        .track-screen {
+            font-weight: 200;
+            color: aqua;
+        }
+    }
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8802a032f..cdfeebe66 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -22,7 +22,7 @@ import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { NodeSelection } from 'prosemirror-state';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -60,7 +60,7 @@ export class DashFieldView {
                             return;
                         }
                     }
-                    tBox.setFocus(state.selection.to + 1);
+                    tBox.setFocus(state.selection.to);
                 }
             }
         };
@@ -84,6 +84,7 @@ export class DashFieldView {
                 width={node.attrs.width}
                 height={node.attrs.height}
                 hideKey={node.attrs.hideKey}
+                hideValue={node.attrs.hideValue}
                 editable={node.attrs.editable}
                 expanded={this.Expanded}
                 dataDoc={node.attrs.dataDoc}
@@ -112,6 +113,7 @@ interface IDashFieldViewInternal {
     fieldKey: string;
     docId: string;
     hideKey: boolean;
+    hideValue: boolean;
     tbox: FormattedTextBox;
     width: number;
     height: number;
@@ -217,12 +219,28 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     };
 
     toggleFieldHide = undoable(
-        action(() => this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])),
+        action(() => {
+            const editor = this._props.tbox.EditorView!;
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: !this._props.node.attrs.hideKey ? true : false }));
+        }),
         'hideKey'
     );
 
+    toggleValueHide = undoable(
+        action(() => {
+            const editor = this._props.tbox.EditorView!;
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: !this._props.node.attrs.hideValue ? true : false }));
+        }),
+        'hideValue'
+    );
+
+    @observable _showValue = false;
     @computed get _hideKey() {
-        return this._dashDoc?.[this._fieldKey + '_hideKey'] && !this._expanded;
+        return this._props.hideKey && !this._expanded;
+    }
+
+    @computed get _hideValue() {
+        return !this._showValue && this._props.hideValue && !this._expanded;
     }
 
     // clicking on the label creates a pivot view collection of all documents
@@ -231,7 +249,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
             DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
+            DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
             DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+            this._dashDoc?.[this._fieldKey + '_hideValue'] && runInAction(() => (this._showValue = !this._showValue));
+            const editor = this._props.tbox.EditorView!;
+            setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
         });
     };
 
@@ -257,12 +279,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     height: this._props.height,
                     pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
-                {this._props.hideKey || this._hideKey ? null : (
+                {this._hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
                         {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
                     </span>
                 )}
-                {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+                {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
                 {/* {!this.values.length ? null : (
                     <select className="dashFieldView-select" tabIndex={-1} onChange={this.selectVal}>
                         {this.values.map(val => (
@@ -279,6 +301,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: DashFieldViewMenu;
     static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
     static toggleFieldHide: () => void = emptyFunction;
+    static toggleValueHide: () => void = emptyFunction;
     constructor(props: any) {
         super(props);
         DashFieldViewMenu.Instance = this;
@@ -292,6 +315,10 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         DashFieldViewMenu.toggleFieldHide();
         DashFieldViewMenu.Instance.fadeOut(true);
     };
+    toggleValueHide = (e: React.MouseEvent) => {
+        DashFieldViewMenu.toggleValueHide();
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
 
     @observable _fieldKey = '';
 
@@ -308,16 +335,27 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     render() {
         return this.getElement(
             <>
-                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.showFields}>
-                        <FontAwesomeIcon icon="eye" size="lg" />
-                    </button>
-                </Tooltip>
-                <Tooltip key="trash" title={<div className="dash-tooltip">Toggle view of field key</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
-                        <FontAwesomeIcon icon="bullseye" size="lg" />
-                    </button>
-                </Tooltip>
+                {!this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+                            <FontAwesomeIcon icon="eye" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+                            <FontAwesomeIcon icon="bullseye" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+                            <FontAwesomeIcon icon="hashtag" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
             </>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f06e5fad0..39d4893ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -104,7 +104,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _recordingStart: number = 0;
     private _ignoreScroll = false;
-    private _hadDownFocus = false;
     private _focusSpeed: Opt<number>;
     private _keymap: any = undefined;
     private _rules: RichTextRules | undefined;
@@ -328,7 +327,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (node.type === this._editorView?.state.schema.nodes.dashField) {
             const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
             const fieldKey = StrCast(node.attrs.fieldKey);
-            return fieldKey + ':' + Field.toJavascriptString(refDoc[fieldKey] as Field);
+            return (
+                (node.attrs.hideKey ? '' : '"' + fieldKey + '"' + ':') + //
+                (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+            );
         }
         return '';
     };
@@ -383,7 +385,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     } else {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((layoutData ?? protoData ?? prevData).Data)));
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
                         ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
                         unchanged = false;
                     }
@@ -1363,7 +1365,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
                 if (pdfAnchor instanceof Doc) {
                     const dashField = view.state.schema.nodes.paragraph.create({}, [
-                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [
+                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false, expanded: true }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
                                 title: StrCast(pdfAnchor.title),
@@ -1577,7 +1579,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 (this.ProseRef?.children?.[0] as any).focus();
             }
         }
-        this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
@@ -1588,9 +1589,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     onPointerUp = (e: React.PointerEvent): void => {
         const editor = this._editorView!;
         const state = editor?.state;
-        if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime) && !this._hadDownFocus) {
-            (this.ProseRef?.children[0] as HTMLElement)?.blur?.();
-        }
         if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
         if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
         else if (this._props.isContentActive() && !e.button) {
@@ -1599,10 +1597,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
                 xpos = xpos - 1;
             }
-            editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
-            let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
-            while (target && !target.dataset?.targethrefs) target = target.parentElement;
-            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+            let node: any;
+            try {
+                node = state.doc.nodeAt(xpos);
+            } catch (e) {}
+            if (node?.type !== schema.nodes.dashFieldView) {
+                editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
+                let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+                while (target && !target.dataset?.targethrefs) target = target.parentElement;
+                FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+            } else {
+                editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
+            }
         }
     };
     @action
@@ -1800,7 +1806,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             default:
                 if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
             case ' ':
-                if (e.code !== 'Space') {
+                if (e.code !== 'Space' && e.code !== 'Backspace') {
                     [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
                         this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
                 }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 7afe91e94..418ec92ef 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -137,6 +137,7 @@ export class RichTextRules {
                 textDocInline.title = inlineFieldKey; // give the annotation its own title
                 textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                 textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+                textDocInline.isDataDoc = true;
                 textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
                 textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
                 textDoc[inlineFieldKey] = ''; // set a default value for the annotation
@@ -349,7 +350,7 @@ export class RichTextRules {
                         if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
                     }
                     const target = docTitle ? getTitledDoc(docTitle) : undefined;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc });
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false, dataDoc });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
                 },
                 { inCode: true }
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 905146ee2..6862eecef 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -264,6 +264,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             fieldKey: { default: '' },
             docId: { default: '' },
             hideKey: { default: false },
+            hideValue: { default: false },
             editable: { default: true },
             expanded: { default: null },
             dataDoc: { default: false },
-- 
cgit v1.2.3-70-g09d2


From ddaad0a2119dcb0dedf432aaff69abe5c92ac991 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Mar 2024 10:29:21 -0400
Subject: enabled %<notestyle> to switch note types.  fixed linking to
 top-level templates (postit, idea, etc).  customized postit and idea style.

---
 src/client/documents/Documents.ts                         |  1 +
 src/client/util/CurrentUserUtils.ts                       |  6 +++---
 src/client/views/StyleProvider.tsx                        |  5 ++++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  5 +++--
 src/client/views/nodes/formattedText/RichTextRules.ts     | 12 ++++++++++--
 5 files changed, 21 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b3d14bc22..a71437346 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -276,6 +276,7 @@ export class DocumentOptions {
     layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected');
     layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
     layout_borderRounding?: string;
+    _layout_borderRounding?: STRt = new StrInfo('amount of rounding to document view corners');
     _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false);
     _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
     _layout_reflowVertical?: BOOLt = new BoolInfo('permit vertical resizing with content "reflow"');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f4599f04a..331b8d515 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -121,9 +121,9 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
-            { title: "Postit", backgroundColor: "yellow",    icon: "sticky-note"},
-            { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb" },
-            { title: "Topic",  backgroundColor: "lightblue", icon: "book-open" }];
+            { title: "Postit", backgroundColor: "yellow",    icon: "sticky-note", _layout_showTitle: "title", layout_borderRounding: "5px"},
+            { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb"  , _layout_showTitle: "title"},
+            { title: "Topic",  backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
         const reqdNoteList = [...reqdTempOpts.map(opts => {
             const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
             const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 7a15e67e4..bf6f2a4fa 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -171,7 +171,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             const backColor = backgroundCol();
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
-            return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
+            console.log(doc?.title);
+            const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
+            return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
         case StyleProp.BorderPath:
             const borderPath = Doc.IsComicStyle(doc) &&
                 props?.renderDepth &&
@@ -199,6 +201,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.BackgroundColor: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
             let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+            if (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
             // prettier-ignore
             switch (doc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 39d4893ab..a0de822ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -246,8 +246,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        if (!pinProps && this._editorView?.state.selection.empty) return this.Document;
-        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
+        const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+        if (!pinProps && this._editorView?.state.selection.empty) return rootDoc;
+        const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
         this.addDocument(anchor);
         this._finishingLink = true;
         this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 418ec92ef..682d4f96f 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,6 +1,6 @@
 import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
 import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { Doc, FieldResult, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
@@ -244,10 +244,18 @@ export class RichTextRules {
             }),
 
             // activate a style by name using prefix '%<color name>'
-            new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
+            new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
                 const marks = RichTextMenu.Instance._brushMap.get(color);
 
+                if (
+                    DocListCast((Doc.UserDoc().template_notes as Doc).data)
+                        .map(d => StrCast(d.title))
+                        .includes(color)
+                ) {
+                    setTimeout(() => this.TextBox.DocumentView?.().switchViews(true, color));
+                    return state.tr.deleteRange(start, end);
+                }
                 if (marks) {
                     const tr = state.tr.deleteRange(start, end);
                     return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
-- 
cgit v1.2.3-70-g09d2


From ad1b41beaa0c55487d41381bddd80ab1a76e7fb5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 00:36:19 -0400
Subject: added Labels button for turning ink labels on/off.  added Pixels and
 Rotate buttons for images.  enabled ink as template for text : check for ink
 stroke data on layout doc if it's not on data doc & started to have style
 provider check for properties on layout doc instead of data (background
 color, boxShadow).   fixed toggling key/value on and off when node is
 selected.

---
 src/client/documents/Documents.ts                  |  2 ++
 src/client/util/CurrentUserUtils.ts                | 16 ++++++++---
 src/client/views/InkControlPtHandles.tsx           |  4 +--
 src/client/views/InkingStroke.tsx                  | 12 ++++++--
 src/client/views/StyleProvider.tsx                 | 17 ++++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +--
 src/client/views/global/globalScripts.ts           | 21 ++++++++++++--
 src/client/views/nodes/DocumentView.scss           |  1 -
 src/client/views/nodes/MapBox/MapBox.tsx           |  3 ++
 .../views/nodes/formattedText/DashFieldView.tsx    | 32 ++++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  3 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |  1 -
 13 files changed, 74 insertions(+), 44 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6275de405..6965ae0ce 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1804,10 +1804,12 @@ export namespace DocUtils {
         const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data);
         const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data);
         const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
+        const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data);
         const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
         const allTemplates = iconViews
             .concat(templBtns)
             .concat(noteTypes)
+            .concat(userTypes)
             .concat(clickFuncs)
             .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
             .filter(doc => doc.isTemplateDoc);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 90368c1eb..3855bdeb6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -764,6 +764,7 @@ pie title Minerals in my tap water
             { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
+            { title: "Labels", toolTip: "Lab els",                          btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels",                         scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
             { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
             { title: "Ink",    toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} },
         ];
@@ -772,7 +773,7 @@ pie title Minerals in my tap water
     static schemaTools():Button[] {
         return [
             {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
-            {title: "1 Line",  toolTip: "Single Line Rows",       btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+            {title: "1 Line",  toolTip: "Single Line Rows",       btnType: ButtonType.ToggleButton, icon: "eye",      scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
             {title: "DataViz", toolTip: "Turn Schema Table into Data Visualization Doc",       btnType: ButtonType.ClickButton, icon: "chart-bar", scripts:{ onClick: '{ datavizFromSchema()'} }, ];
     }
 
@@ -780,12 +781,18 @@ pie title Minerals in my tap water
         return [
             { title: "Back",    toolTip: "Go back",         btnType: ButtonType.ClickButton,  icon: "arrow-left",              scripts: { onClick: '{ return webBack(); }' }},
             { title: "Forward", toolTip: "Go forward",      btnType: ButtonType.ClickButton,  icon: "arrow-right",             scripts: { onClick: '{ return webForward(); }'}},
-            { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
+            { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script:  '{ return webSetURL(value, _readOnly_); }'} },
         ];
     }
     static videoTools() {
         return [
-            { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton,  icon: "camera",                  scripts: { onClick: '{ return videoSnapshot(); }' }},
+            { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton,  icon: "camera",   scripts: { onClick: '{ return videoSnapshot(); }' }},
+        ];
+    }
+    static imageTools() {
+        return [
+            { title: "Pixels",toolTip: "Set Native Pixel Sizze", btnType: ButtonType.ClickButton,  icon: "portrait",  scripts: { onClick: 'imageSetPixelSize();' }},
+            { title: "Rotate",toolTip: "Rotate 90",              btnType: ButtonType.ClickButton,  icon: "redo-alt",  scripts: { onClick: 'imageRotate90();' }},
         ];
     }
     static contextMenuTools():Button[] {
@@ -810,7 +817,8 @@ pie title Minerals in my tap water
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
-            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
             { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
            ];
     }
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 01d52135a..edc6b404b 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -239,8 +239,8 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
         const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => (
             <circle
                 key={key}
-                cx={pt.X}
-                cy={pt.Y}
+                cx={pt?.X}
+                cy={pt?.Y}
                 r={this._props.screenSpaceLineWidth * 2}
                 fill={this._overStart ? '#aaaaaa' : '#99999977'}
                 stroke={'#00007777'}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 345309b4a..5a9f10ba4 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -217,7 +217,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * factor for converting between ink and screen space.
      */
     inkScaledData = () => {
-        const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null))?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
@@ -446,7 +446,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
                 </svg>
-                {!closed || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+                {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
                     <div
                         className="inkStroke-text"
                         style={{
@@ -464,7 +464,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             yPadding={10}
                             xPadding={10}
                             fieldKey="text"
-                            dontRegisterView={true}
+                            //dontRegisterView={true}
                             noSidebar={true}
                             dontScale={true}
                             isContentActive={this._props.isContentActive}
@@ -488,6 +488,9 @@ export function SetActiveInkColor(value: string) {
 export function SetActiveIsInkMask(value: boolean) {
     ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
 }
+export function SetActiveInkHideTextLabels(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
 export function SetActiveFillColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
 }
@@ -515,6 +518,9 @@ export function ActiveFillColor(): string {
 export function ActiveIsInkMask(): boolean {
     return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
 }
+export function ActiveInkHideTextLabels(): boolean {
+    return BoolCast(ActiveInkPen().activeInkHideTextLabels, false);
+}
 export function ActiveArrowStart(): string {
     return StrCast(ActiveInkPen()?.activeArrowStart, '');
 }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index bf6f2a4fa..8073d0842 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -89,7 +89,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     const isNonTransparent = property.includes(':nonTransparent');
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isAnnotated = property.includes(':annotated');
-    const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
+    const layoutDoc = doc ? Doc.Layout(doc) : doc;
+    const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
     const isEmpty = property.includes(':empty');
     const boxBackground = property.includes(':box');
@@ -129,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         highlightStyle: doc.isGroup ? "dotted": highlightStyle,
                         highlightColor,
                         highlightIndex,
-                        highlightStroke: doc.layout_isSvg,
+                        highlightStroke: layoutDoc?.layout_isSvg,
                     };
                 }
             }
@@ -203,7 +204,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
             if (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
             // prettier-ignore
-            switch (doc?.type) {
+            switch (layoutDoc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
                 case DocumentType.PRES:        docColor = docColor || 'transparent'; break;
                 case DocumentType.FONTICON:    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
@@ -220,13 +221,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 case DocumentType.SCREENSHOT:
                 case DocumentType.VID:         docColor = docColor || (Colors.LIGHT_GRAY); break;
                 case DocumentType.COL:
-                    docColor = docColor || (Doc.IsSystem(doc)
+                    docColor = docColor || (doc && Doc.IsSystem(doc)
                             ? SettingsManager.userBackgroundColor
-                            : doc.annotationOn
+                            : doc?.annotationOn
                             ? '#00000010' // faint interior for collections on PDFs, images, etc
                             : doc?.isGroup
                             ? undefined
-                            : doc._type_collection === CollectionViewType.Stacking ?
+                            : doc?._type_collection === CollectionViewType.Stacking ?
                             (Colors.DARK_GRAY)
                             : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
                     break;
@@ -241,7 +242,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !doc.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
@@ -266,7 +267,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                         : fieldKey.includes('_inline')  // if doc is an inline document in a text box
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
-                        : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline)
+                        : DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline)
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                         : StrCast(doc.layout_boxShadow, '');
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5495735ea..c53bad509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -649,7 +649,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                       y: B.y - inkWidth / 2,
                       _width: B.width + inkWidth,
                       _height: B.height + inkWidth,
-                      stroke_showLabel: true}, // prettier-ignore
+                      stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
                     inkWidth
                 );
                 if (Doc.ActiveTool === InkTool.Write) {
@@ -1721,7 +1721,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
                 vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
             });
-        SnappingManager.addSnapLines(horizLines, vertLines);
+        this.layoutDoc._freeform_snapLines && SnappingManager.addSnapLines(horizLines, vertLines);
     };
 
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index d785c155b..3ea7bfbcd 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -12,7 +12,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
+import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 // import { InkTranscription } from '../InkTranscription';
 import { DocData } from '../../../fields/DocSymbols';
@@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { ImageBox } from '../nodes/ImageBox';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
@@ -336,15 +337,20 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
 ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
+ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
     const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
     // prettier-ignore
-    const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
+    const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
             checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
             setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
             setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
         }],
+        ['labels', {
+            checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
+            setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel),
+            setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
+        }],
         ['fillColor', {
             checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
             setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
@@ -399,6 +405,15 @@ ScriptingGlobals.add(function videoSnapshot() {
     selected?.Snapshot();
 });
 
+ScriptingGlobals.add(function imageSetPixelSize() {
+    const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+    selected?.setNativeSize();
+});
+ScriptingGlobals.add(function imageRotate90() {
+    const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+    selected?.rotate();
+});
+
 /** Schema
  *      toggleSchemaPreview
  **/
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5421c1b50..4fea6b3c6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -256,6 +256,5 @@
 
     .documentView-node:first-child {
         position: relative;
-        background: '#B59B66'; //$white;
     }
 }
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 36dad2747..b73898f59 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1161,6 +1161,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                         <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
                             <TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={(e: any) => this.handleSearchChange(e.target.value)} />
                             <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+                            <div style={{ opacity: 0 }}>
+                                <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+                            </div>
                         </div>
                     )}
                     {this._settingsOpen && !this._routeToAnimate && (
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index cdfeebe66..f6ca97ee9 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -29,8 +29,8 @@ export class DashFieldView {
     root: any;
     node: any;
     tbox: FormattedTextBox;
-    @observable expanded = false;
-    Expanded = () => this.expanded;
+    @observable _nodeSelected = false;
+    NodeSelected = () => this._nodeSelected;
 
     unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
@@ -71,8 +71,6 @@ export class DashFieldView {
             e.stopPropagation();
         };
 
-        this.expanded = node.attrs.expanded;
-
         this.root = ReactDOM.createRoot(this.dom);
         this.root.render(
             <DashFieldViewInternal
@@ -86,7 +84,7 @@ export class DashFieldView {
                 hideKey={node.attrs.hideKey}
                 hideValue={node.attrs.hideValue}
                 editable={node.attrs.editable}
-                expanded={this.Expanded}
+                nodeSelected={this.NodeSelected}
                 dataDoc={node.attrs.dataDoc}
                 tbox={tbox}
             />
@@ -100,11 +98,11 @@ export class DashFieldView {
         });
     }
     deselectNode() {
-        runInAction(() => (this.expanded = false));
+        runInAction(() => (this._nodeSelected = false));
         this.dom.classList.remove('ProseMirror-selectednode');
     }
     selectNode() {
-        setTimeout(() => runInAction(() => (this.expanded = true)), 100);
+        setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
         this.dom.classList.add('ProseMirror-selectednode');
     }
 }
@@ -118,7 +116,7 @@ interface IDashFieldViewInternal {
     width: number;
     height: number;
     editable: boolean;
-    expanded: () => boolean;
+    nodeSelected: () => boolean;
     dataDoc: boolean;
     node: any;
     getPos: any;
@@ -132,7 +130,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     _fieldKey: string;
     _fieldRef = React.createRef<HTMLDivElement>();
     @observable _dashDoc: Doc | undefined = undefined;
-    @observable _expanded = this._props.expanded();
+    @observable _expanded = this._props.nodeSelected();
 
     constructor(props: IDashFieldViewInternal) {
         super(props);
@@ -158,7 +156,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
-    isRowActive = () => (this._props.expanded() || this._expanded) && this._props.editable;
+    isRowActive = () => (this._props.nodeSelected() || this._expanded) && this._props.editable;
 
     finishEdit = action(() => {
         if (this._expanded) {
@@ -181,7 +179,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
                     maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
-                    columnWidth={this._expanded || this._props.expanded() ? this.columnWidth : returnZero}
+                    columnWidth={this._expanded || this._props.nodeSelected() ? this.columnWidth : returnZero}
                     selectedCell={this.selectedCell}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
@@ -190,7 +188,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     getFinfo={emptyFunction}
                     setColumnValues={returnFalse}
                     allowCRs={true}
-                    oneLine={!this._expanded && !this._props.expanded()}
+                    oneLine={!this._expanded && !this._props.nodeSelected()}
                     finishEdit={this.finishEdit}
                     transform={Transform.Identity}
                     menuTarget={null}
@@ -221,7 +219,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     toggleFieldHide = undoable(
         action(() => {
             const editor = this._props.tbox.EditorView!;
-            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: !this._props.node.attrs.hideKey ? true : false }));
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
         }),
         'hideKey'
     );
@@ -229,18 +227,17 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     toggleValueHide = undoable(
         action(() => {
             const editor = this._props.tbox.EditorView!;
-            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: !this._props.node.attrs.hideValue ? true : false }));
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
         }),
         'hideValue'
     );
 
-    @observable _showValue = false;
     @computed get _hideKey() {
         return this._props.hideKey && !this._expanded;
     }
 
     @computed get _hideValue() {
-        return !this._showValue && this._props.hideValue && !this._expanded;
+        return this._props.hideValue && !this._props.nodeSelected();
     }
 
     // clicking on the label creates a pivot view collection of all documents
@@ -251,7 +248,6 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
             DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
             DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
             DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
-            this._dashDoc?.[this._fieldKey + '_hideValue'] && runInAction(() => (this._showValue = !this._showValue));
             const editor = this._props.tbox.EditorView!;
             setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
         });
@@ -263,7 +259,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     };
 
     @computed get values() {
-        if (this._props.expanded()) return [];
+        if (this._props.nodeSelected()) return [];
         const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
 
         return vals.strings.map(facet => ({ value: facet, label: facet }));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0de822ac..3b583771a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1366,7 +1366,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
                 if (pdfAnchor instanceof Doc) {
                     const dashField = view.state.schema.nodes.paragraph.create({}, [
-                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false, expanded: true }, undefined, [
+                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
                                 title: StrCast(pdfAnchor.title),
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 682d4f96f..483035e86 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -250,10 +250,11 @@ export class RichTextRules {
 
                 if (
                     DocListCast((Doc.UserDoc().template_notes as Doc).data)
+                        .concat(DocListCast((Doc.UserDoc().template_user as Doc).data))
                         .map(d => StrCast(d.title))
                         .includes(color)
                 ) {
-                    setTimeout(() => this.TextBox.DocumentView?.().switchViews(true, color));
+                    setTimeout(() => this.TextBox.DocumentView?.().switchViews(true, color, undefined, true));
                     return state.tr.deleteRange(start, end);
                 }
                 if (marks) {
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 6862eecef..e335044ea 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -266,7 +266,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             hideKey: { default: false },
             hideValue: { default: false },
             editable: { default: true },
-            expanded: { default: null },
             dataDoc: { default: false },
         },
         leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
-- 
cgit v1.2.3-70-g09d2


From a9baa52c557faaa82fd3f696f0d603dd10b982a8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 02:18:07 -0400
Subject: updated mermaids template. updated paintFunc to also render the
 underlying code text so that the paintFunc will update if dashFields in the
 code change.  fixed schema views to render templates properly in preview. 
 fixed text to only modify the data doc.  changed setPixels in imageBox to not
 consider scaling of outer containers.

---
 src/client/util/CurrentUserUtils.ts                          |  4 ++--
 src/client/views/StyleProvider.tsx                           |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  6 ++++--
 .../views/collections/collectionSchema/SchemaTableCell.tsx   |  6 +++---
 src/client/views/nodes/ImageBox.tsx                          |  3 +--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx    | 12 ++++++++----
 6 files changed, 19 insertions(+), 14 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3855bdeb6..dbf9641b6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -321,9 +321,9 @@ export class CurrentUserUtils {
                         {type:"text",text:" "},
                         {type:"text",text:"Minerals in my tap water"},
                         {type:"text",text:"\n    \"Calcium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:false,hideValue:false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:true,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Potassium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,hideValue:false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:true,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Magnesium\" : 10.01"}
                     ]}
                 ]},
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8073d0842..c8b0c0560 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -139,7 +139,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
-        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamilyq'], StrCast(Doc.UserDoc().fontFamily));
+        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'],  StrCast(Doc.UserDoc().fontWeight));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
         case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c53bad509..1b562939b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -76,7 +76,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
     @computed get paintFunc() {
-        const field = this.layoutDoc[this.fieldKey];
+        const field = this.dataDoc[this.fieldKey];
         const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
         return !paintFunc
             ? ''
@@ -1870,7 +1870,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
                     height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
                 }}>
-                {this.paintFunc ? null : this._lightboxDoc ? (
+                {this.paintFunc ? (
+                    <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+                ) : this._lightboxDoc ? (
                     <div style={{ padding: 15, width: '100%', height: '100%' }}>
                         <DocumentView
                             {...this._props}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index ab05a62d3..bf36b2668 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -98,7 +98,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             focus: emptyFunction,
             addDocTab: SchemaTableCell.addFieldDoc,
             pinToPres: returnZero,
-            Document,
+            Document: DocCast(Document.rootDocument, Document),
             fieldKey: fieldKey,
             PanelWidth: columnWidth,
             PanelHeight: props.rowHeight,
@@ -134,14 +134,14 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                     contents={undefined}
                     fieldContents={fieldProps}
                     editing={this.selected ? undefined : false}
-                    GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey, SnappingManager.MetaKey)}
+                    GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)}
                     SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
                             this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                             this._props.finishEdit?.();
                             return true;
                         }
-                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
+                        const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
                         this._props.finishEdit?.();
                         return ret;
                     }, 'edit schema cell')}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 86e8ed60a..469869e21 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,8 +165,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
-        const nscale = NumCast(this._props.PanelWidth()) / scaling;
+        const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
         this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3b583771a..b815342e3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -326,10 +326,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     leafText = (node: Node) => {
         if (node.type === this._editorView?.state.schema.nodes.dashField) {
-            const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+            const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
             const fieldKey = StrCast(node.attrs.fieldKey);
             return (
-                (node.attrs.hideKey ? '' : '"' + fieldKey + '"' + ':') + //
+                (node.attrs.hideKey ? '' : fieldKey + ':') + //
                 (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
             );
         }
@@ -346,7 +346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     tryUpdateDoc = (force: boolean) => {
         if (this._editorView && (this._editorView as any).docView) {
             const state = this._editorView.state;
-            const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+            const dataDoc = this.dataDoc;
             const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
             const newJson = JSON.stringify(state.toJSON());
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
@@ -1608,7 +1608,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 while (target && !target.dataset?.targethrefs) target = target.parentElement;
                 FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
             } else {
-                editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
+                try {
+                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
+                } catch (e) {
+                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1))));
+                }
             }
         }
     };
-- 
cgit v1.2.3-70-g09d2


From c43413ab9733d63a2321d43c1eb9c9a54bf85d71 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 11:23:40 -0400
Subject: audio box fixes

---
 package-lock.json                                  | 3469 +++++++++++---------
 package.json                                       |    2 +-
 src/client/util/CurrentUserUtils.ts                |    2 +-
 src/client/views/nodes/AudioBox.tsx                |   11 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |    4 +-
 5 files changed, 1903 insertions(+), 1585 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index cf8e8ce29..c417b7193 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
                 "@types/pdf-parse": "^1.1.4",
                 "@types/reveal": "^4.2.0",
                 "@types/supercluster": "^7.1.3",
-                "@types/web": "^0.0.138",
+                "@types/web": "^0.0.142",
                 "@webscopeio/react-textarea-autocomplete": "^4.9.2",
                 "adm-zip": "^0.5.10",
                 "archiver": "^6.0.1",
@@ -390,13 +390,13 @@
             }
         },
         "node_modules/@ampproject/remapping": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
-            "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+            "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
             "peer": true,
             "dependencies": {
-                "@jridgewell/gen-mapping": "^0.3.0",
-                "@jridgewell/trace-mapping": "^0.3.9"
+                "@jridgewell/gen-mapping": "^0.3.5",
+                "@jridgewell/trace-mapping": "^0.3.24"
             },
             "engines": {
                 "node": ">=6.0.0"
@@ -414,24 +414,24 @@
             }
         },
         "node_modules/@azure/core-auth": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.6.0.tgz",
-            "integrity": "sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==",
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.1.tgz",
+            "integrity": "sha512-dyeQwvgthqs/SlPVQbZQetpslXceHd4i5a7M/7z/lGEAVwnSluabnQOjF2/dk/hhWgMISusv1Ytp4mQ8JNy62A==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
                 "@azure/core-util": "^1.1.0",
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz",
-            "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
@@ -470,39 +470,39 @@
             }
         },
         "node_modules/@azure/core-lro": {
-            "version": "2.6.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.6.0.tgz",
-            "integrity": "sha512-PyRNcaIOfMgoUC01/24NoG+k8O81VrKxYARnDlo+Q2xji0/0/j2nIt8BwQh294pb1c5QnXTDPbNR4KzoDKXEoQ==",
+            "version": "2.7.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.1.tgz",
+            "integrity": "sha512-kXSlrNHOCTVZMxpXNRqzgh9/j4cnNXU5Hf2YjMyjddRhCXFiFRzmNaqwN+XO9rGTsCOIaaG7M67zZdyliXZG9g==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
                 "@azure/core-util": "^1.2.0",
                 "@azure/logger": "^1.0.0",
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz",
-            "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/core-paging": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
-            "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+            "version": "1.6.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.1.tgz",
+            "integrity": "sha512-3tKIQXSU3mlN+ITz0m2pXLnKK3oQ6/EVcW8ud011Iq+M0rx6Wnm7NUEpoMeOAEedeKlPtemrQzO6YWoDR71O5w==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/core-tracing": {
@@ -518,37 +518,37 @@
             }
         },
         "node_modules/@azure/core-util": {
-            "version": "1.7.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.7.0.tgz",
-            "integrity": "sha512-Zq2i3QO6k9DA8vnm29mYM4G8IE9u1mhF1GUabVEqPNX8Lj833gdxQ2NAFxt2BZsfAL+e9cT8SyVN7dFVJ/Hf0g==",
+            "version": "1.8.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.8.1.tgz",
+            "integrity": "sha512-L3voj0StUdJ+YKomvwnTv7gHzguJO+a6h30pmmZdRprJCM+RJlGMPxzuh4R7lhQu1jNmEtaHX5wvTgWLDAmbGQ==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/core-util/node_modules/@azure/abort-controller": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz",
-            "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
                 "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/logger": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
-            "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.1.tgz",
+            "integrity": "sha512-/+4TtokaGgC+MnThdf6HyIH9Wrjp+CnCn3Nx3ggevN7FFjjNyjqg0yLlc2i9S+Z2uAzI8GYOo35Nzb1MhQ89MA==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.6.2"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=18.0.0"
             }
         },
         "node_modules/@azure/storage-blob": {
@@ -570,41 +570,41 @@
             }
         },
         "node_modules/@babel/code-frame": {
-            "version": "7.23.5",
-            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
-            "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+            "version": "7.24.2",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
+            "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
             "dependencies": {
-                "@babel/highlight": "^7.23.4",
-                "chalk": "^2.4.2"
+                "@babel/highlight": "^7.24.2",
+                "picocolors": "^1.0.0"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/compat-data": {
-            "version": "7.23.5",
-            "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
-            "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz",
+            "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==",
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/core": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
-            "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
+            "version": "7.24.3",
+            "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz",
+            "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
             "peer": true,
             "dependencies": {
                 "@ampproject/remapping": "^2.2.0",
-                "@babel/code-frame": "^7.23.5",
-                "@babel/generator": "^7.23.6",
+                "@babel/code-frame": "^7.24.2",
+                "@babel/generator": "^7.24.1",
                 "@babel/helper-compilation-targets": "^7.23.6",
                 "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helpers": "^7.23.9",
-                "@babel/parser": "^7.23.9",
-                "@babel/template": "^7.23.9",
-                "@babel/traverse": "^7.23.9",
-                "@babel/types": "^7.23.9",
+                "@babel/helpers": "^7.24.1",
+                "@babel/parser": "^7.24.1",
+                "@babel/template": "^7.24.0",
+                "@babel/traverse": "^7.24.1",
+                "@babel/types": "^7.24.0",
                 "convert-source-map": "^2.0.0",
                 "debug": "^4.1.0",
                 "gensync": "^1.0.0-beta.2",
@@ -620,13 +620,13 @@
             }
         },
         "node_modules/@babel/generator": {
-            "version": "7.23.6",
-            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
-            "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz",
+            "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==",
             "dependencies": {
-                "@babel/types": "^7.23.6",
-                "@jridgewell/gen-mapping": "^0.3.2",
-                "@jridgewell/trace-mapping": "^0.3.17",
+                "@babel/types": "^7.24.0",
+                "@jridgewell/gen-mapping": "^0.3.5",
+                "@jridgewell/trace-mapping": "^0.3.25",
                 "jsesc": "^2.5.1"
             },
             "engines": {
@@ -671,16 +671,16 @@
             }
         },
         "node_modules/@babel/helper-create-class-features-plugin": {
-            "version": "7.23.10",
-            "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz",
-            "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz",
+            "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
                 "@babel/helper-member-expression-to-functions": "^7.23.0",
                 "@babel/helper-optimise-call-expression": "^7.22.5",
-                "@babel/helper-replace-supers": "^7.22.20",
+                "@babel/helper-replace-supers": "^7.24.1",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
                 "@babel/helper-split-export-declaration": "^7.22.6",
                 "semver": "^6.3.1"
@@ -709,9 +709,9 @@
             }
         },
         "node_modules/@babel/helper-define-polyfill-provider": {
-            "version": "0.5.0",
-            "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
-            "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz",
+            "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==",
             "dependencies": {
                 "@babel/helper-compilation-targets": "^7.22.6",
                 "@babel/helper-plugin-utils": "^7.22.5",
@@ -766,11 +766,11 @@
             }
         },
         "node_modules/@babel/helper-module-imports": {
-            "version": "7.22.15",
-            "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
-            "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+            "version": "7.24.3",
+            "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
+            "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
             "dependencies": {
-                "@babel/types": "^7.22.15"
+                "@babel/types": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -806,9 +806,9 @@
             }
         },
         "node_modules/@babel/helper-plugin-utils": {
-            "version": "7.22.5",
-            "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
-            "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+            "version": "7.24.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
+            "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
             "engines": {
                 "node": ">=6.9.0"
             }
@@ -830,12 +830,12 @@
             }
         },
         "node_modules/@babel/helper-replace-supers": {
-            "version": "7.22.20",
-            "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
-            "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz",
+            "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==",
             "dependencies": {
                 "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-member-expression-to-functions": "^7.22.15",
+                "@babel/helper-member-expression-to-functions": "^7.23.0",
                 "@babel/helper-optimise-call-expression": "^7.22.5"
             },
             "engines": {
@@ -879,9 +879,9 @@
             }
         },
         "node_modules/@babel/helper-string-parser": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
-            "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
+            "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
             "engines": {
                 "node": ">=6.9.0"
             }
@@ -916,36 +916,37 @@
             }
         },
         "node_modules/@babel/helpers": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
-            "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz",
+            "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==",
             "peer": true,
             "dependencies": {
-                "@babel/template": "^7.23.9",
-                "@babel/traverse": "^7.23.9",
-                "@babel/types": "^7.23.9"
+                "@babel/template": "^7.24.0",
+                "@babel/traverse": "^7.24.1",
+                "@babel/types": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/highlight": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
-            "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+            "version": "7.24.2",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
+            "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
             "dependencies": {
                 "@babel/helper-validator-identifier": "^7.22.20",
                 "chalk": "^2.4.2",
-                "js-tokens": "^4.0.0"
+                "js-tokens": "^4.0.0",
+                "picocolors": "^1.0.0"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/parser": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
-            "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
+            "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
             "bin": {
                 "parser": "bin/babel-parser.js"
             },
@@ -954,11 +955,11 @@
             }
         },
         "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz",
-            "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz",
+            "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -968,13 +969,13 @@
             }
         },
         "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz",
-            "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz",
+            "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
-                "@babel/plugin-transform-optional-chaining": "^7.23.3"
+                "@babel/plugin-transform-optional-chaining": "^7.24.1"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -984,12 +985,12 @@
             }
         },
         "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
-            "version": "7.23.7",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz",
-            "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz",
+            "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==",
             "dependencies": {
                 "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1068,11 +1069,11 @@
             }
         },
         "node_modules/@babel/plugin-syntax-import-assertions": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz",
-            "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz",
+            "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1082,11 +1083,11 @@
             }
         },
         "node_modules/@babel/plugin-syntax-import-attributes": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz",
-            "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz",
+            "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1118,11 +1119,11 @@
             }
         },
         "node_modules/@babel/plugin-syntax-jsx": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
-            "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
+            "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1241,11 +1242,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-arrow-functions": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz",
-            "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz",
+            "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1255,12 +1256,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-async-generator-functions": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz",
-            "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==",
+            "version": "7.24.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz",
+            "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==",
             "dependencies": {
                 "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-remap-async-to-generator": "^7.22.20",
                 "@babel/plugin-syntax-async-generators": "^7.8.4"
             },
@@ -1272,12 +1273,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-async-to-generator": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz",
-            "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz",
+            "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==",
             "dependencies": {
-                "@babel/helper-module-imports": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-module-imports": "^7.24.1",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-remap-async-to-generator": "^7.22.20"
             },
             "engines": {
@@ -1288,11 +1289,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-block-scoped-functions": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz",
-            "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz",
+            "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1302,11 +1303,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-block-scoping": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz",
-            "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz",
+            "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1316,12 +1317,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-class-properties": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz",
-            "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz",
+            "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==",
             "dependencies": {
-                "@babel/helper-create-class-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-create-class-features-plugin": "^7.24.1",
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1331,12 +1332,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-class-static-block": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz",
-            "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz",
+            "integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==",
             "dependencies": {
-                "@babel/helper-create-class-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-create-class-features-plugin": "^7.24.1",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-class-static-block": "^7.14.5"
             },
             "engines": {
@@ -1347,16 +1348,16 @@
             }
         },
         "node_modules/@babel/plugin-transform-classes": {
-            "version": "7.23.8",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz",
-            "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
+            "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
                 "@babel/helper-compilation-targets": "^7.23.6",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
-                "@babel/helper-plugin-utils": "^7.22.5",
-                "@babel/helper-replace-supers": "^7.22.20",
+                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-replace-supers": "^7.24.1",
                 "@babel/helper-split-export-declaration": "^7.22.6",
                 "globals": "^11.1.0"
             },
@@ -1368,12 +1369,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-computed-properties": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz",
-            "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz",
+            "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
-                "@babel/template": "^7.22.15"
+                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/template": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1383,11 +1384,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-destructuring": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz",
-            "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
+            "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1397,12 +1398,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-dotall-regex": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz",
-            "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz",
+            "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==",
             "dependencies": {
                 "@babel/helper-create-regexp-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1412,11 +1413,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-duplicate-keys": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz",
-            "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz",
+            "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1426,11 +1427,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-dynamic-import": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz",
-            "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz",
+            "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-dynamic-import": "^7.8.3"
             },
             "engines": {
@@ -1441,12 +1442,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-exponentiation-operator": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz",
-            "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz",
+            "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==",
             "dependencies": {
                 "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1456,11 +1457,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-export-namespace-from": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz",
-            "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz",
+            "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
             },
             "engines": {
@@ -1471,11 +1472,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-for-of": {
-            "version": "7.23.6",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz",
-            "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz",
+            "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
             },
             "engines": {
@@ -1486,13 +1487,13 @@
             }
         },
         "node_modules/@babel/plugin-transform-function-name": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz",
-            "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz",
+            "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==",
             "dependencies": {
-                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-compilation-targets": "^7.23.6",
                 "@babel/helper-function-name": "^7.23.0",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1502,11 +1503,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-json-strings": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz",
-            "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz",
+            "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-json-strings": "^7.8.3"
             },
             "engines": {
@@ -1517,11 +1518,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-literals": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz",
-            "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz",
+            "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1531,11 +1532,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-logical-assignment-operators": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz",
-            "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz",
+            "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
             },
             "engines": {
@@ -1546,11 +1547,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-member-expression-literals": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz",
-            "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz",
+            "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1560,12 +1561,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-modules-amd": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz",
-            "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz",
+            "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==",
             "dependencies": {
                 "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1575,12 +1576,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-modules-commonjs": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz",
-            "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz",
+            "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==",
             "dependencies": {
                 "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-simple-access": "^7.22.5"
             },
             "engines": {
@@ -1591,13 +1592,13 @@
             }
         },
         "node_modules/@babel/plugin-transform-modules-systemjs": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz",
-            "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz",
+            "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==",
             "dependencies": {
                 "@babel/helper-hoist-variables": "^7.22.5",
                 "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-validator-identifier": "^7.22.20"
             },
             "engines": {
@@ -1608,12 +1609,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-modules-umd": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz",
-            "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz",
+            "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==",
             "dependencies": {
                 "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1638,11 +1639,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-new-target": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz",
-            "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz",
+            "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1652,11 +1653,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz",
-            "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz",
+            "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
             },
             "engines": {
@@ -1667,11 +1668,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-numeric-separator": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz",
-            "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz",
+            "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-numeric-separator": "^7.10.4"
             },
             "engines": {
@@ -1682,15 +1683,14 @@
             }
         },
         "node_modules/@babel/plugin-transform-object-rest-spread": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
-            "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
+            "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
             "dependencies": {
-                "@babel/compat-data": "^7.23.3",
-                "@babel/helper-compilation-targets": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-compilation-targets": "^7.23.6",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
-                "@babel/plugin-transform-parameters": "^7.23.3"
+                "@babel/plugin-transform-parameters": "^7.24.1"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1700,12 +1700,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-object-super": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz",
-            "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz",
+            "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
-                "@babel/helper-replace-supers": "^7.22.20"
+                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-replace-supers": "^7.24.1"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1715,11 +1715,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-optional-catch-binding": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz",
-            "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz",
+            "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
             },
             "engines": {
@@ -1730,11 +1730,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-optional-chaining": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz",
-            "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
+            "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
                 "@babel/plugin-syntax-optional-chaining": "^7.8.3"
             },
@@ -1746,11 +1746,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-parameters": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz",
-            "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
+            "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1760,12 +1760,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-private-methods": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz",
-            "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz",
+            "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==",
             "dependencies": {
-                "@babel/helper-create-class-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-create-class-features-plugin": "^7.24.1",
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1775,13 +1775,13 @@
             }
         },
         "node_modules/@babel/plugin-transform-private-property-in-object": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz",
-            "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
+            "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
-                "@babel/helper-create-class-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-create-class-features-plugin": "^7.24.1",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
             },
             "engines": {
@@ -1792,11 +1792,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-property-literals": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz",
-            "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz",
+            "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1806,11 +1806,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-react-display-name": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz",
-            "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz",
+            "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1852,12 +1852,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-react-pure-annotations": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz",
-            "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz",
+            "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1867,11 +1867,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-regenerator": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz",
-            "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz",
+            "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "regenerator-transform": "^0.15.2"
             },
             "engines": {
@@ -1882,11 +1882,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-reserved-words": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz",
-            "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz",
+            "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1896,11 +1896,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-shorthand-properties": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz",
-            "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz",
+            "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1910,11 +1910,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-spread": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz",
-            "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz",
+            "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
             },
             "engines": {
@@ -1925,11 +1925,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-sticky-regex": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz",
-            "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz",
+            "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1939,11 +1939,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-template-literals": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz",
-            "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz",
+            "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1953,11 +1953,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-typeof-symbol": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz",
-            "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
+            "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1967,11 +1967,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-unicode-escapes": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz",
-            "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz",
+            "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1981,12 +1981,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-unicode-property-regex": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz",
-            "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz",
+            "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==",
             "dependencies": {
                 "@babel/helper-create-regexp-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1996,12 +1996,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-unicode-regex": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz",
-            "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz",
+            "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==",
             "dependencies": {
                 "@babel/helper-create-regexp-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -2011,12 +2011,12 @@
             }
         },
         "node_modules/@babel/plugin-transform-unicode-sets-regex": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz",
-            "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz",
+            "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==",
             "dependencies": {
                 "@babel/helper-create-regexp-features-plugin": "^7.22.15",
-                "@babel/helper-plugin-utils": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -2026,25 +2026,25 @@
             }
         },
         "node_modules/@babel/preset-env": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz",
-            "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==",
+            "version": "7.24.3",
+            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz",
+            "integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==",
             "dependencies": {
-                "@babel/compat-data": "^7.23.5",
+                "@babel/compat-data": "^7.24.1",
                 "@babel/helper-compilation-targets": "^7.23.6",
-                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.24.0",
                 "@babel/helper-validator-option": "^7.23.5",
-                "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
-                "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
-                "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7",
+                "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
+                "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
+                "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
                 "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
                 "@babel/plugin-syntax-async-generators": "^7.8.4",
                 "@babel/plugin-syntax-class-properties": "^7.12.13",
                 "@babel/plugin-syntax-class-static-block": "^7.14.5",
                 "@babel/plugin-syntax-dynamic-import": "^7.8.3",
                 "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
-                "@babel/plugin-syntax-import-assertions": "^7.23.3",
-                "@babel/plugin-syntax-import-attributes": "^7.23.3",
+                "@babel/plugin-syntax-import-assertions": "^7.24.1",
+                "@babel/plugin-syntax-import-attributes": "^7.24.1",
                 "@babel/plugin-syntax-import-meta": "^7.10.4",
                 "@babel/plugin-syntax-json-strings": "^7.8.3",
                 "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
@@ -2056,58 +2056,58 @@
                 "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
                 "@babel/plugin-syntax-top-level-await": "^7.14.5",
                 "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
-                "@babel/plugin-transform-arrow-functions": "^7.23.3",
-                "@babel/plugin-transform-async-generator-functions": "^7.23.9",
-                "@babel/plugin-transform-async-to-generator": "^7.23.3",
-                "@babel/plugin-transform-block-scoped-functions": "^7.23.3",
-                "@babel/plugin-transform-block-scoping": "^7.23.4",
-                "@babel/plugin-transform-class-properties": "^7.23.3",
-                "@babel/plugin-transform-class-static-block": "^7.23.4",
-                "@babel/plugin-transform-classes": "^7.23.8",
-                "@babel/plugin-transform-computed-properties": "^7.23.3",
-                "@babel/plugin-transform-destructuring": "^7.23.3",
-                "@babel/plugin-transform-dotall-regex": "^7.23.3",
-                "@babel/plugin-transform-duplicate-keys": "^7.23.3",
-                "@babel/plugin-transform-dynamic-import": "^7.23.4",
-                "@babel/plugin-transform-exponentiation-operator": "^7.23.3",
-                "@babel/plugin-transform-export-namespace-from": "^7.23.4",
-                "@babel/plugin-transform-for-of": "^7.23.6",
-                "@babel/plugin-transform-function-name": "^7.23.3",
-                "@babel/plugin-transform-json-strings": "^7.23.4",
-                "@babel/plugin-transform-literals": "^7.23.3",
-                "@babel/plugin-transform-logical-assignment-operators": "^7.23.4",
-                "@babel/plugin-transform-member-expression-literals": "^7.23.3",
-                "@babel/plugin-transform-modules-amd": "^7.23.3",
-                "@babel/plugin-transform-modules-commonjs": "^7.23.3",
-                "@babel/plugin-transform-modules-systemjs": "^7.23.9",
-                "@babel/plugin-transform-modules-umd": "^7.23.3",
+                "@babel/plugin-transform-arrow-functions": "^7.24.1",
+                "@babel/plugin-transform-async-generator-functions": "^7.24.3",
+                "@babel/plugin-transform-async-to-generator": "^7.24.1",
+                "@babel/plugin-transform-block-scoped-functions": "^7.24.1",
+                "@babel/plugin-transform-block-scoping": "^7.24.1",
+                "@babel/plugin-transform-class-properties": "^7.24.1",
+                "@babel/plugin-transform-class-static-block": "^7.24.1",
+                "@babel/plugin-transform-classes": "^7.24.1",
+                "@babel/plugin-transform-computed-properties": "^7.24.1",
+                "@babel/plugin-transform-destructuring": "^7.24.1",
+                "@babel/plugin-transform-dotall-regex": "^7.24.1",
+                "@babel/plugin-transform-duplicate-keys": "^7.24.1",
+                "@babel/plugin-transform-dynamic-import": "^7.24.1",
+                "@babel/plugin-transform-exponentiation-operator": "^7.24.1",
+                "@babel/plugin-transform-export-namespace-from": "^7.24.1",
+                "@babel/plugin-transform-for-of": "^7.24.1",
+                "@babel/plugin-transform-function-name": "^7.24.1",
+                "@babel/plugin-transform-json-strings": "^7.24.1",
+                "@babel/plugin-transform-literals": "^7.24.1",
+                "@babel/plugin-transform-logical-assignment-operators": "^7.24.1",
+                "@babel/plugin-transform-member-expression-literals": "^7.24.1",
+                "@babel/plugin-transform-modules-amd": "^7.24.1",
+                "@babel/plugin-transform-modules-commonjs": "^7.24.1",
+                "@babel/plugin-transform-modules-systemjs": "^7.24.1",
+                "@babel/plugin-transform-modules-umd": "^7.24.1",
                 "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
-                "@babel/plugin-transform-new-target": "^7.23.3",
-                "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
-                "@babel/plugin-transform-numeric-separator": "^7.23.4",
-                "@babel/plugin-transform-object-rest-spread": "^7.23.4",
-                "@babel/plugin-transform-object-super": "^7.23.3",
-                "@babel/plugin-transform-optional-catch-binding": "^7.23.4",
-                "@babel/plugin-transform-optional-chaining": "^7.23.4",
-                "@babel/plugin-transform-parameters": "^7.23.3",
-                "@babel/plugin-transform-private-methods": "^7.23.3",
-                "@babel/plugin-transform-private-property-in-object": "^7.23.4",
-                "@babel/plugin-transform-property-literals": "^7.23.3",
-                "@babel/plugin-transform-regenerator": "^7.23.3",
-                "@babel/plugin-transform-reserved-words": "^7.23.3",
-                "@babel/plugin-transform-shorthand-properties": "^7.23.3",
-                "@babel/plugin-transform-spread": "^7.23.3",
-                "@babel/plugin-transform-sticky-regex": "^7.23.3",
-                "@babel/plugin-transform-template-literals": "^7.23.3",
-                "@babel/plugin-transform-typeof-symbol": "^7.23.3",
-                "@babel/plugin-transform-unicode-escapes": "^7.23.3",
-                "@babel/plugin-transform-unicode-property-regex": "^7.23.3",
-                "@babel/plugin-transform-unicode-regex": "^7.23.3",
-                "@babel/plugin-transform-unicode-sets-regex": "^7.23.3",
+                "@babel/plugin-transform-new-target": "^7.24.1",
+                "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
+                "@babel/plugin-transform-numeric-separator": "^7.24.1",
+                "@babel/plugin-transform-object-rest-spread": "^7.24.1",
+                "@babel/plugin-transform-object-super": "^7.24.1",
+                "@babel/plugin-transform-optional-catch-binding": "^7.24.1",
+                "@babel/plugin-transform-optional-chaining": "^7.24.1",
+                "@babel/plugin-transform-parameters": "^7.24.1",
+                "@babel/plugin-transform-private-methods": "^7.24.1",
+                "@babel/plugin-transform-private-property-in-object": "^7.24.1",
+                "@babel/plugin-transform-property-literals": "^7.24.1",
+                "@babel/plugin-transform-regenerator": "^7.24.1",
+                "@babel/plugin-transform-reserved-words": "^7.24.1",
+                "@babel/plugin-transform-shorthand-properties": "^7.24.1",
+                "@babel/plugin-transform-spread": "^7.24.1",
+                "@babel/plugin-transform-sticky-regex": "^7.24.1",
+                "@babel/plugin-transform-template-literals": "^7.24.1",
+                "@babel/plugin-transform-typeof-symbol": "^7.24.1",
+                "@babel/plugin-transform-unicode-escapes": "^7.24.1",
+                "@babel/plugin-transform-unicode-property-regex": "^7.24.1",
+                "@babel/plugin-transform-unicode-regex": "^7.24.1",
+                "@babel/plugin-transform-unicode-sets-regex": "^7.24.1",
                 "@babel/preset-modules": "0.1.6-no-external-plugins",
-                "babel-plugin-polyfill-corejs2": "^0.4.8",
-                "babel-plugin-polyfill-corejs3": "^0.9.0",
-                "babel-plugin-polyfill-regenerator": "^0.5.5",
+                "babel-plugin-polyfill-corejs2": "^0.4.10",
+                "babel-plugin-polyfill-corejs3": "^0.10.4",
+                "babel-plugin-polyfill-regenerator": "^0.6.1",
                 "core-js-compat": "^3.31.0",
                 "semver": "^6.3.1"
             },
@@ -2132,16 +2132,16 @@
             }
         },
         "node_modules/@babel/preset-react": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz",
-            "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz",
+            "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.22.5",
-                "@babel/helper-validator-option": "^7.22.15",
-                "@babel/plugin-transform-react-display-name": "^7.23.3",
-                "@babel/plugin-transform-react-jsx": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-validator-option": "^7.23.5",
+                "@babel/plugin-transform-react-display-name": "^7.24.1",
+                "@babel/plugin-transform-react-jsx": "^7.23.4",
                 "@babel/plugin-transform-react-jsx-development": "^7.22.5",
-                "@babel/plugin-transform-react-pure-annotations": "^7.23.3"
+                "@babel/plugin-transform-react-pure-annotations": "^7.24.1"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -2156,9 +2156,9 @@
             "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
         },
         "node_modules/@babel/runtime": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
-            "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
+            "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
             "dependencies": {
                 "regenerator-runtime": "^0.14.0"
             },
@@ -2167,9 +2167,9 @@
             }
         },
         "node_modules/@babel/runtime-corejs3": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz",
-            "integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.1.tgz",
+            "integrity": "sha512-T9ko/35G+Bkl+win48GduaPlhSlOjjE5s1TeiEcD+QpxlLQnoEfb/nO/T+TQqkm+ipFwORn+rB8w14iJ/uD0bg==",
             "dependencies": {
                 "core-js-pure": "^3.30.2",
                 "regenerator-runtime": "^0.14.0"
@@ -2179,31 +2179,31 @@
             }
         },
         "node_modules/@babel/template": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
-            "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
+            "version": "7.24.0",
+            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
+            "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
             "dependencies": {
                 "@babel/code-frame": "^7.23.5",
-                "@babel/parser": "^7.23.9",
-                "@babel/types": "^7.23.9"
+                "@babel/parser": "^7.24.0",
+                "@babel/types": "^7.24.0"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/traverse": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz",
-            "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
+            "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
             "dependencies": {
-                "@babel/code-frame": "^7.23.5",
-                "@babel/generator": "^7.23.6",
+                "@babel/code-frame": "^7.24.1",
+                "@babel/generator": "^7.24.1",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
                 "@babel/helper-hoist-variables": "^7.22.5",
                 "@babel/helper-split-export-declaration": "^7.22.6",
-                "@babel/parser": "^7.23.9",
-                "@babel/types": "^7.23.9",
+                "@babel/parser": "^7.24.1",
+                "@babel/types": "^7.24.0",
                 "debug": "^4.3.1",
                 "globals": "^11.1.0"
             },
@@ -2212,9 +2212,9 @@
             }
         },
         "node_modules/@babel/types": {
-            "version": "7.23.9",
-            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
-            "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
+            "version": "7.24.0",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
+            "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
             "dependencies": {
                 "@babel/helper-string-parser": "^7.23.4",
                 "@babel/helper-validator-identifier": "^7.22.20",
@@ -2300,9 +2300,9 @@
             "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
         },
         "node_modules/@emotion/is-prop-valid": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
-            "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+            "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
             "dependencies": {
                 "@emotion/memoize": "^0.8.1"
             }
@@ -2313,9 +2313,9 @@
             "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
         },
         "node_modules/@emotion/react": {
-            "version": "11.11.3",
-            "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz",
-            "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==",
+            "version": "11.11.4",
+            "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz",
+            "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==",
             "dependencies": {
                 "@babel/runtime": "^7.18.3",
                 "@emotion/babel-plugin": "^11.11.0",
@@ -2336,9 +2336,9 @@
             }
         },
         "node_modules/@emotion/serialize": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz",
-            "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==",
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz",
+            "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==",
             "dependencies": {
                 "@emotion/hash": "^0.9.1",
                 "@emotion/memoize": "^0.8.1",
@@ -2353,14 +2353,14 @@
             "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
         },
         "node_modules/@emotion/styled": {
-            "version": "11.11.0",
-            "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz",
-            "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==",
+            "version": "11.11.5",
+            "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz",
+            "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==",
             "dependencies": {
                 "@babel/runtime": "^7.18.3",
                 "@emotion/babel-plugin": "^11.11.0",
-                "@emotion/is-prop-valid": "^1.2.1",
-                "@emotion/serialize": "^1.1.2",
+                "@emotion/is-prop-valid": "^1.2.2",
+                "@emotion/serialize": "^1.1.4",
                 "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
                 "@emotion/utils": "^1.2.1"
             },
@@ -2500,9 +2500,9 @@
             }
         },
         "node_modules/@eslint/js": {
-            "version": "8.56.0",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
-            "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+            "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,
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2553,13 +2553,13 @@
             }
         },
         "node_modules/@floating-ui/react": {
-            "version": "0.26.9",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.9.tgz",
-            "integrity": "sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w==",
+            "version": "0.26.10",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.10.tgz",
+            "integrity": "sha512-sh6f9gVvWQdEzLObrWbJ97c0clJObiALsFe0LiR/kb3tDRKwEhObASEH2QyfdoO/ZBPzwxa9j+nYFo+sqgbioA==",
             "dependencies": {
-                "@floating-ui/react-dom": "^2.0.8",
-                "@floating-ui/utils": "^0.2.1",
-                "tabbable": "^6.0.1"
+                "@floating-ui/react-dom": "^2.0.0",
+                "@floating-ui/utils": "^0.2.0",
+                "tabbable": "^6.0.0"
             },
             "peerDependencies": {
                 "react": ">=16.8.0",
@@ -2921,11 +2921,11 @@
             }
         },
         "node_modules/@jimp/bmp": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.10.tgz",
-            "integrity": "sha512-1UXRl1Nw1KptZ1r0ANqtXOst9vGH51dq7keVKQzyyTO2lz4dOaezS9StuSTNh+RmiHg/SVPaFRpPfB0S/ln4Kg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
+            "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "bmp-js": "^0.1.0"
             },
             "peerDependencies": {
@@ -2933,11 +2933,11 @@
             }
         },
         "node_modules/@jimp/core": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.10.tgz",
-            "integrity": "sha512-ZKyrehVy6wu1PnBXIUpn/fXmyMRQiVSbvHDubgXz4bfTOao3GiOurKHjByutQIgozuAN6ZHWiSge1dKA+dex3w==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz",
+            "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "any-base": "^1.1.0",
                 "buffer": "^5.2.0",
                 "exif-parser": "^0.1.12",
@@ -2948,19 +2948,19 @@
             }
         },
         "node_modules/@jimp/custom": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.10.tgz",
-            "integrity": "sha512-sPZkUYe1hu0iIgNisjizxPJqq2vaaKvkCkPoXq2U6UV3ZA1si/WVdrg25da3IcGIEV+83AoHgM8TvqlLgrCJsg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
+            "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
             "dependencies": {
-                "@jimp/core": "^0.22.10"
+                "@jimp/core": "^0.22.12"
             }
         },
         "node_modules/@jimp/gif": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.10.tgz",
-            "integrity": "sha512-yEX2dSpamvkSx1PPDWGnKeWDrBz0vrCKjVG/cn4Zr68MRRT75tbZIeOrBa+RiUpY3ho5ix7d36LkYvt3qfUIhQ==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz",
+            "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "gifwrap": "^0.10.1",
                 "omggif": "^1.0.9"
             },
@@ -2969,11 +2969,11 @@
             }
         },
         "node_modules/@jimp/jpeg": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.10.tgz",
-            "integrity": "sha512-6bu98pAcVN4DY2oiDLC4TOgieX/lZrLd1tombWZOFCN5PBmqaHQxm7IUmT+Wj4faEvh8QSHgVLSA+2JQQRJWVA==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz",
+            "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "jpeg-js": "^0.4.4"
             },
             "peerDependencies": {
@@ -2981,44 +2981,44 @@
             }
         },
         "node_modules/@jimp/plugin-blit": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.10.tgz",
-            "integrity": "sha512-6EI8Sl+mxYHEIy6Yteh6eknD+EZguKpNdr3sCKxNezmLR0+vK99vHcllo6uGSjXXiwtwS67Xqxn8SsoatL+UJQ==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
+            "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-blur": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.10.tgz",
-            "integrity": "sha512-4XRTWuPVdMXJeclJMisXPGizeHtTryVaVV5HnuQXpKqIZtzXReCCpNGH8q/i0kBQOQMXhGWS3mpqOEwtpPePKw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz",
+            "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-circle": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.10.tgz",
-            "integrity": "sha512-mhcwTO1ywRxiCgtLGge6tDDIDPlX6qkI3CY+BjgGG/XhVHccCddXgOGLdlf+5OuKIEF2Nqs0V01LQEQIJFTmEw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz",
+            "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-color": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.10.tgz",
-            "integrity": "sha512-e4t3L7Kedd96E0x1XjsTM6NcgulKUU66HdFTao7Tc9FYJRFSlttARZ/C6LEryGDm/i69R6bJEpo7BkNz0YL55Q==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
+            "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "tinycolor2": "^1.6.0"
             },
             "peerDependencies": {
@@ -3026,11 +3026,11 @@
             }
         },
         "node_modules/@jimp/plugin-contain": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.10.tgz",
-            "integrity": "sha512-eP8KrzctuEoqibQAxi9WhbnoRosydhiwg+IYya3dKuKDBTrD9UHt+ERlPQ/lTNWHzV/l4S1ntV3r9s9saJgsXA==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz",
+            "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3040,11 +3040,11 @@
             }
         },
         "node_modules/@jimp/plugin-cover": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.10.tgz",
-            "integrity": "sha512-kJCwL5T1igfa0InCfkE7bBeqg26m46aoRt10ug+rvm11P6RrvRMGrgINFyIKB+mnB7CiyBN/MOula1CvLhSInQ==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz",
+            "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3054,55 +3054,55 @@
             }
         },
         "node_modules/@jimp/plugin-crop": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.10.tgz",
-            "integrity": "sha512-BOZ+YGaZlhU7c5ye65RxikicXH0Ki0It6/XHISvipR5WZrfjLjL2Ke20G+AGnwBQc76gKenVcMXVUCnEjtZV+Q==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
+            "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-displace": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.10.tgz",
-            "integrity": "sha512-llNiWWMTKISDXt5+cXI0GaFmZWAjlT+4fFLYf4eXquuL/9wZoQsEBhv2GdGd48mkiS8jZq1Nnb2Q4ehEPTvrzw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz",
+            "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-dither": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.10.tgz",
-            "integrity": "sha512-05WLmeV5M+P/0FS+bWf13hMew2X0oa8w9AtmevL2UyA/5GqiyvP2Xm5WfGQ8oFiiMvpnL6RFomJQOZtWca0C2w==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz",
+            "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-fisheye": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.10.tgz",
-            "integrity": "sha512-InjiXvc7Gkzrx8VWtU97kDqV7ENnhHGPULymJWeZaF2aicud9Fpk4iCtd/DcZIrk7Cbe60A8RwNXN00HXIbSCg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz",
+            "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-flip": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.10.tgz",
-            "integrity": "sha512-42GkGtTHWnhnwTMPVK/kXObZbkYIpQWfuIfy5EMEMk6zRj05zpv4vsjkKWfuemweZINwfvD7wDJF7FVFNNcZZg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz",
+            "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3110,55 +3110,55 @@
             }
         },
         "node_modules/@jimp/plugin-gaussian": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.10.tgz",
-            "integrity": "sha512-ykrG/6lTp9Q5YA8jS5XzwMHtRxb9HOFMgtmnrUZ8kU+BK8REecfy9Ic5BUEOjCYvS1a/xLsnrZQU07iiYxBxFg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz",
+            "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-invert": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.10.tgz",
-            "integrity": "sha512-d8j9BlUJYs/c994t4azUWSWmQq4LLPG4ecm8m6SSNqap+S/HlVQGqjYhJEBbY9EXkOTYB9vBL9bqwSM1Rr6paA==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz",
+            "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-mask": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.10.tgz",
-            "integrity": "sha512-yRBs1230XZkz24uFTdTcSlZ0HXZpIWzM3iFQN56MzZ7USgdVZjPPDCQ8I9RpqfZ36nDflQkUO0wV7ucsi4ogow==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz",
+            "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-normalize": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.10.tgz",
-            "integrity": "sha512-Wk9GX6eJMchX/ZAazVa70Fagu+OXMvHiPY+HrcEwcclL+p1wo8xAHEsf9iKno7Ja4EU9lLhbBRY5hYJyiKMEkg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz",
+            "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-print": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.10.tgz",
-            "integrity": "sha512-1U3VloIR+beE1kWPdGEJMiE2h1Do29iv3w8sBbvPyRP4qXxRFcDpmCGtctsrKmb1krlBFlj8ubyAY90xL+5n9w==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz",
+            "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "load-bmfont": "^1.4.1"
             },
             "peerDependencies": {
@@ -3167,22 +3167,22 @@
             }
         },
         "node_modules/@jimp/plugin-resize": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.10.tgz",
-            "integrity": "sha512-ixomxVcnAONXDgaq0opvAx4UAOiEhOA/tipuhFFOvPKFd4yf1BAnEviB5maB0SBHHkJXPUSzDp/73xVTMGSe7g==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
+            "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5"
             }
         },
         "node_modules/@jimp/plugin-rotate": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.10.tgz",
-            "integrity": "sha512-eeFX8dnRyf3LAdsdXWKWuN18hLRg8zy1cP0cP9rHzQVWRK7ck/QsLxK1vHq7MADGwQalNaNTJ9SQxH6c8mz6jw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
+            "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3192,11 +3192,11 @@
             }
         },
         "node_modules/@jimp/plugin-scale": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.10.tgz",
-            "integrity": "sha512-TG/H0oUN69C9ArBCZg4PmuoixFVKIiru8282KzSB/Tp1I0xwX0XLTv3dJ5pobPlIgPcB+TmD4xAIdkCT4rtWxg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
+            "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3204,11 +3204,11 @@
             }
         },
         "node_modules/@jimp/plugin-shadow": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.10.tgz",
-            "integrity": "sha512-TN9xm6fI7XfxbMUQqFPZjv59Xdpf0tSiAQdINB4g6pJMWiVANR/74OtDONoy3KKpenu5Y38s+FkrtID/KcQAhw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz",
+            "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3217,11 +3217,11 @@
             }
         },
         "node_modules/@jimp/plugin-threshold": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.10.tgz",
-            "integrity": "sha512-DA2lSnU0TgIRbAgmXaxroYw3Ad6J2DOFEoJp0NleSm2h3GWbZEE5yW9U2B6hD3iqn4AenG4E2b2WzHXZyzSutw==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz",
+            "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10"
+                "@jimp/utils": "^0.22.12"
             },
             "peerDependencies": {
                 "@jimp/custom": ">=0.3.5",
@@ -3230,31 +3230,31 @@
             }
         },
         "node_modules/@jimp/plugins": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.10.tgz",
-            "integrity": "sha512-KDMZyM6pmvS8freB+UBLko1TO/k4D7URS/nphCozuH+P7i3UMe7NdckXKJ8u+WD6sqN0YFYvBehpkpnUiw/91w==",
-            "dependencies": {
-                "@jimp/plugin-blit": "^0.22.10",
-                "@jimp/plugin-blur": "^0.22.10",
-                "@jimp/plugin-circle": "^0.22.10",
-                "@jimp/plugin-color": "^0.22.10",
-                "@jimp/plugin-contain": "^0.22.10",
-                "@jimp/plugin-cover": "^0.22.10",
-                "@jimp/plugin-crop": "^0.22.10",
-                "@jimp/plugin-displace": "^0.22.10",
-                "@jimp/plugin-dither": "^0.22.10",
-                "@jimp/plugin-fisheye": "^0.22.10",
-                "@jimp/plugin-flip": "^0.22.10",
-                "@jimp/plugin-gaussian": "^0.22.10",
-                "@jimp/plugin-invert": "^0.22.10",
-                "@jimp/plugin-mask": "^0.22.10",
-                "@jimp/plugin-normalize": "^0.22.10",
-                "@jimp/plugin-print": "^0.22.10",
-                "@jimp/plugin-resize": "^0.22.10",
-                "@jimp/plugin-rotate": "^0.22.10",
-                "@jimp/plugin-scale": "^0.22.10",
-                "@jimp/plugin-shadow": "^0.22.10",
-                "@jimp/plugin-threshold": "^0.22.10",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz",
+            "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==",
+            "dependencies": {
+                "@jimp/plugin-blit": "^0.22.12",
+                "@jimp/plugin-blur": "^0.22.12",
+                "@jimp/plugin-circle": "^0.22.12",
+                "@jimp/plugin-color": "^0.22.12",
+                "@jimp/plugin-contain": "^0.22.12",
+                "@jimp/plugin-cover": "^0.22.12",
+                "@jimp/plugin-crop": "^0.22.12",
+                "@jimp/plugin-displace": "^0.22.12",
+                "@jimp/plugin-dither": "^0.22.12",
+                "@jimp/plugin-fisheye": "^0.22.12",
+                "@jimp/plugin-flip": "^0.22.12",
+                "@jimp/plugin-gaussian": "^0.22.12",
+                "@jimp/plugin-invert": "^0.22.12",
+                "@jimp/plugin-mask": "^0.22.12",
+                "@jimp/plugin-normalize": "^0.22.12",
+                "@jimp/plugin-print": "^0.22.12",
+                "@jimp/plugin-resize": "^0.22.12",
+                "@jimp/plugin-rotate": "^0.22.12",
+                "@jimp/plugin-scale": "^0.22.12",
+                "@jimp/plugin-shadow": "^0.22.12",
+                "@jimp/plugin-threshold": "^0.22.12",
                 "timm": "^1.6.1"
             },
             "peerDependencies": {
@@ -3262,11 +3262,11 @@
             }
         },
         "node_modules/@jimp/png": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.10.tgz",
-            "integrity": "sha512-RYinU7tZToeeR2g2qAMn42AU+8OUHjXPKZZ9RkmoL4bguA1xyZWaSdr22/FBkmnHhOERRlr02KPDN1OTOYHLDQ==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz",
+            "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==",
             "dependencies": {
-                "@jimp/utils": "^0.22.10",
+                "@jimp/utils": "^0.22.12",
                 "pngjs": "^6.0.0"
             },
             "peerDependencies": {
@@ -3274,9 +3274,9 @@
             }
         },
         "node_modules/@jimp/tiff": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.10.tgz",
-            "integrity": "sha512-OaivlSYzpNTHyH/h7pEtl3A7F7TbsgytZs52GLX/xITW92ffgDgT6PkldIrMrET6ERh/hdijNQiew7IoEEr2og==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz",
+            "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==",
             "dependencies": {
                 "utif2": "^4.0.1"
             },
@@ -3285,15 +3285,15 @@
             }
         },
         "node_modules/@jimp/types": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.10.tgz",
-            "integrity": "sha512-u/r+XYzbCx4zZukDmxx8S0er3Yq3iDPI6+31WKX0N18i2qPPJYcn8qwIFurfupRumGvJ8SlGLCgt/T+Y8zzUIw==",
-            "dependencies": {
-                "@jimp/bmp": "^0.22.10",
-                "@jimp/gif": "^0.22.10",
-                "@jimp/jpeg": "^0.22.10",
-                "@jimp/png": "^0.22.10",
-                "@jimp/tiff": "^0.22.10",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz",
+            "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==",
+            "dependencies": {
+                "@jimp/bmp": "^0.22.12",
+                "@jimp/gif": "^0.22.12",
+                "@jimp/jpeg": "^0.22.12",
+                "@jimp/png": "^0.22.12",
+                "@jimp/tiff": "^0.22.12",
                 "timm": "^1.6.1"
             },
             "peerDependencies": {
@@ -3301,9 +3301,9 @@
             }
         },
         "node_modules/@jimp/utils": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.10.tgz",
-            "integrity": "sha512-ztlOK9Mm2iLG2AMoabzM4i3WZ/FtshcgsJCbZCRUs/DKoeS2tySRJTnQZ1b7Roq0M4Ce+FUAxnCAcBV0q7PH9w==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
+            "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
             "dependencies": {
                 "regenerator-runtime": "^0.13.3"
             }
@@ -3314,13 +3314,13 @@
             "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
         },
         "node_modules/@jridgewell/gen-mapping": {
-            "version": "0.3.3",
-            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-            "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+            "version": "0.3.5",
+            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+            "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
             "dependencies": {
-                "@jridgewell/set-array": "^1.0.1",
+                "@jridgewell/set-array": "^1.2.1",
                 "@jridgewell/sourcemap-codec": "^1.4.10",
-                "@jridgewell/trace-mapping": "^0.3.9"
+                "@jridgewell/trace-mapping": "^0.3.24"
             },
             "engines": {
                 "node": ">=6.0.0"
@@ -3335,20 +3335,20 @@
             }
         },
         "node_modules/@jridgewell/set-array": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-            "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+            "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
             "engines": {
                 "node": ">=6.0.0"
             }
         },
         "node_modules/@jridgewell/source-map": {
-            "version": "0.3.5",
-            "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
-            "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
+            "version": "0.3.6",
+            "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
+            "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
             "dependencies": {
-                "@jridgewell/gen-mapping": "^0.3.0",
-                "@jridgewell/trace-mapping": "^0.3.9"
+                "@jridgewell/gen-mapping": "^0.3.5",
+                "@jridgewell/trace-mapping": "^0.3.25"
             }
         },
         "node_modules/@jridgewell/sourcemap-codec": {
@@ -3357,9 +3357,9 @@
             "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
         },
         "node_modules/@jridgewell/trace-mapping": {
-            "version": "0.3.22",
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
-            "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+            "version": "0.3.25",
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+            "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
             "dependencies": {
                 "@jridgewell/resolve-uri": "^3.1.0",
                 "@jridgewell/sourcemap-codec": "^1.4.14"
@@ -3371,9 +3371,9 @@
             "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
         },
         "node_modules/@leichtgewicht/ip-codec": {
-            "version": "2.0.4",
-            "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
-            "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
+            "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
             "dev": true
         },
         "node_modules/@mapbox/geojson-rewind": {
@@ -3408,9 +3408,9 @@
             }
         },
         "node_modules/@mapbox/mapbox-gl-supported": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
-            "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ=="
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz",
+            "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg=="
         },
         "node_modules/@mapbox/node-pre-gyp": {
             "version": "1.0.11",
@@ -3564,22 +3564,22 @@
             }
         },
         "node_modules/@mongodb-js/saslprep": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
-            "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
+            "version": "1.1.5",
+            "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz",
+            "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==",
             "dependencies": {
                 "sparse-bitfield": "^3.0.3"
             }
         },
         "node_modules/@mui/base": {
-            "version": "5.0.0-beta.36",
-            "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz",
-            "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==",
+            "version": "5.0.0-beta.40",
+            "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
+            "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@floating-ui/react-dom": "^2.0.8",
-                "@mui/types": "^7.2.13",
-                "@mui/utils": "^5.15.9",
+                "@mui/types": "^7.2.14",
+                "@mui/utils": "^5.15.14",
                 "@popperjs/core": "^2.11.8",
                 "clsx": "^2.1.0",
                 "prop-types": "^15.8.1"
@@ -3603,18 +3603,18 @@
             }
         },
         "node_modules/@mui/core-downloads-tracker": {
-            "version": "5.15.11",
-            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz",
-            "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz",
+            "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mui-org"
             }
         },
         "node_modules/@mui/icons-material": {
-            "version": "5.15.10",
-            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.10.tgz",
-            "integrity": "sha512-9cF8oUHZKo9oQ7EQ3pxPELaZuZVmphskU4OI6NiJNDVN7zcuvrEsuWjYo1Zh4fLiC39Nrvm30h/B51rcUjvSGA==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz",
+            "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9"
             },
@@ -3637,16 +3637,16 @@
             }
         },
         "node_modules/@mui/material": {
-            "version": "5.15.10",
-            "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.10.tgz",
-            "integrity": "sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz",
+            "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
-                "@mui/base": "5.0.0-beta.36",
-                "@mui/core-downloads-tracker": "^5.15.10",
-                "@mui/system": "^5.15.9",
-                "@mui/types": "^7.2.13",
-                "@mui/utils": "^5.15.9",
+                "@mui/base": "5.0.0-beta.40",
+                "@mui/core-downloads-tracker": "^5.15.14",
+                "@mui/system": "^5.15.14",
+                "@mui/types": "^7.2.14",
+                "@mui/utils": "^5.15.14",
                 "@types/react-transition-group": "^4.4.10",
                 "clsx": "^2.1.0",
                 "csstype": "^3.1.3",
@@ -3681,12 +3681,12 @@
             }
         },
         "node_modules/@mui/private-theming": {
-            "version": "5.15.11",
-            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz",
-            "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
+            "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
-                "@mui/utils": "^5.15.11",
+                "@mui/utils": "^5.15.14",
                 "prop-types": "^15.8.1"
             },
             "engines": {
@@ -3707,9 +3707,9 @@
             }
         },
         "node_modules/@mui/styled-engine": {
-            "version": "5.15.11",
-            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz",
-            "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
+            "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@emotion/cache": "^11.11.0",
@@ -3764,15 +3764,15 @@
             }
         },
         "node_modules/@mui/system": {
-            "version": "5.15.9",
-            "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz",
-            "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz",
+            "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
-                "@mui/private-theming": "^5.15.9",
-                "@mui/styled-engine": "^5.15.9",
-                "@mui/types": "^7.2.13",
-                "@mui/utils": "^5.15.9",
+                "@mui/private-theming": "^5.15.14",
+                "@mui/styled-engine": "^5.15.14",
+                "@mui/types": "^7.2.14",
+                "@mui/utils": "^5.15.14",
                 "clsx": "^2.1.0",
                 "csstype": "^3.1.3",
                 "prop-types": "^15.8.1"
@@ -3803,9 +3803,9 @@
             }
         },
         "node_modules/@mui/types": {
-            "version": "7.2.13",
-            "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz",
-            "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==",
+            "version": "7.2.14",
+            "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
+            "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
             "peerDependencies": {
                 "@types/react": "^17.0.0 || ^18.0.0"
             },
@@ -3816,9 +3816,9 @@
             }
         },
         "node_modules/@mui/utils": {
-            "version": "5.15.11",
-            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz",
-            "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==",
+            "version": "5.15.14",
+            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
+            "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@types/prop-types": "^15.7.11",
@@ -3928,9 +3928,9 @@
             }
         },
         "node_modules/@octokit/openapi-types": {
-            "version": "19.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
-            "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw=="
+            "version": "20.0.0",
+            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
+            "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
         },
         "node_modules/@octokit/request": {
             "version": "8.2.0",
@@ -3960,17 +3960,17 @@
             }
         },
         "node_modules/@octokit/types": {
-            "version": "12.5.0",
-            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.5.0.tgz",
-            "integrity": "sha512-YJEKcb0KkJlIUNU/zjnZwHEP8AoVh/OoIcP/1IyR4UHxExz7fzpe/a8IG4wBtQi7QDEqiomVLX88S6FpxxAJtg==",
+            "version": "12.6.0",
+            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
+            "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
             "dependencies": {
-                "@octokit/openapi-types": "^19.1.0"
+                "@octokit/openapi-types": "^20.0.0"
             }
         },
         "node_modules/@opentelemetry/api": {
-            "version": "1.7.0",
-            "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz",
-            "integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==",
+            "version": "1.8.0",
+            "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz",
+            "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==",
             "engines": {
                 "node": ">=8.0.0"
             }
@@ -6765,9 +6765,9 @@
             }
         },
         "node_modules/@sindresorhus/is": {
-            "version": "6.1.0",
-            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.1.0.tgz",
-            "integrity": "sha512-BuvU07zq3tQ/2SIgBsEuxKYDyDjC0n7Zir52bpHy2xnBbW81+po43aLFPLbeV3HRAheFbGud1qgcqSYfhtHMAg==",
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.2.0.tgz",
+            "integrity": "sha512-yM/IGPkVnYGblhDosFBwq0ZGdnVSBkNV4onUtipGMOjZd4kB6GAu3ys91aftSbyMHh6A2GPdt+KDI5NoWP63MQ==",
             "engines": {
                 "node": ">=16"
             },
@@ -6809,9 +6809,9 @@
             }
         },
         "node_modules/@swc/helpers": {
-            "version": "0.5.6",
-            "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.6.tgz",
-            "integrity": "sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==",
+            "version": "0.5.8",
+            "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.8.tgz",
+            "integrity": "sha512-lruDGw3pnfM3wmZHeW7JuhkGQaJjPyiKjxeGhdmfoOT53Ic9qb5JLDNaK2HUdl1zLDeX28H221UvKjfdvSLVMg==",
             "dependencies": {
                 "tslib": "^2.4.0"
             }
@@ -6833,9 +6833,9 @@
             "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
         },
         "node_modules/@tsconfig/node10": {
-            "version": "1.0.9",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
-            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+            "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
             "dev": true
         },
         "node_modules/@tsconfig/node12": {
@@ -8501,9 +8501,9 @@
             "dev": true
         },
         "node_modules/@types/chai": {
-            "version": "4.3.11",
-            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
-            "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
+            "version": "4.3.14",
+            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz",
+            "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==",
             "dev": true
         },
         "node_modules/@types/color": {
@@ -8555,9 +8555,9 @@
             "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
         },
         "node_modules/@types/cookie-parser": {
-            "version": "1.4.6",
-            "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz",
-            "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==",
+            "version": "1.4.7",
+            "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz",
+            "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*"
@@ -8725,9 +8725,9 @@
             }
         },
         "node_modules/@types/d3-hierarchy": {
-            "version": "3.1.6",
-            "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz",
-            "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==",
+            "version": "3.1.7",
+            "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+            "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
             "dev": true
         },
         "node_modules/@types/d3-interpolate": {
@@ -8837,9 +8837,9 @@
             "integrity": "sha512-zf2GwV/G6TdaLwpLDcGTIkHnXf8JEf/viMux+khqKQKDa8/8BAUtXXZS563GnvJ4Fg0PBLGAaFf2GekEVSZ6GQ=="
         },
         "node_modules/@types/eslint": {
-            "version": "8.56.2",
-            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
-            "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
+            "version": "8.56.6",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz",
+            "integrity": "sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==",
             "dependencies": {
                 "@types/estree": "*",
                 "@types/json-schema": "*"
@@ -8860,9 +8860,9 @@
             "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
         },
         "node_modules/@types/estree-jsx": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz",
-            "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==",
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+            "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
             "dependencies": {
                 "@types/estree": "*"
             }
@@ -8901,9 +8901,9 @@
             }
         },
         "node_modules/@types/express-session": {
-            "version": "1.17.10",
-            "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.10.tgz",
-            "integrity": "sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==",
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz",
+            "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*"
@@ -9055,9 +9055,9 @@
             }
         },
         "node_modules/@types/lodash": {
-            "version": "4.14.202",
-            "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
-            "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
+            "version": "4.17.0",
+            "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+            "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
             "dev": true
         },
         "node_modules/@types/mapbox-gl": {
@@ -9100,9 +9100,9 @@
             "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
         },
         "node_modules/@types/node": {
-            "version": "20.11.20",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
-            "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
+            "version": "20.12.2",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz",
+            "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -9211,14 +9211,14 @@
             "integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg=="
         },
         "node_modules/@types/prop-types": {
-            "version": "15.7.11",
-            "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
-            "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
+            "version": "15.7.12",
+            "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
+            "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
         },
         "node_modules/@types/qs": {
-            "version": "6.9.11",
-            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
-            "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==",
+            "version": "6.9.14",
+            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
+            "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
             "dev": true
         },
         "node_modules/@types/range-parser": {
@@ -9237,12 +9237,11 @@
             }
         },
         "node_modules/@types/react": {
-            "version": "18.2.57",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.57.tgz",
-            "integrity": "sha512-ZvQsktJgSYrQiMirAN60y4O/LRevIV8hUzSOSNB6gfR3/o3wCBFQx3sPwIYtuDMeiVgsSS3UzCV26tEzgnfvQw==",
+            "version": "18.2.73",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.73.tgz",
+            "integrity": "sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==",
             "dependencies": {
                 "@types/prop-types": "*",
-                "@types/scheduler": "*",
                 "csstype": "^3.0.2"
             }
         },
@@ -9256,9 +9255,9 @@
             }
         },
         "node_modules/@types/react-color": {
-            "version": "3.0.11",
-            "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.11.tgz",
-            "integrity": "sha512-20m5GpzmdqwmSdnPeMs4UPPUuvkS4ESwakL6u2YN1hbo+ajWiiTwGYIMGhdcJFGeoLyAsr7TVonbZrMhU3+pdw==",
+            "version": "3.0.12",
+            "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.12.tgz",
+            "integrity": "sha512-pr3uKE3lSvf7GFo1Rn2K3QktiZQFFrSgSGJ/3iMvSOYWt2pPAJ97rVdVfhWxYJZ8prAEXzoP2XX//3qGSQgu7Q==",
             "dev": true,
             "dependencies": {
                 "@types/react": "*",
@@ -9278,9 +9277,9 @@
             }
         },
         "node_modules/@types/react-dom": {
-            "version": "18.2.19",
-            "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
-            "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
+            "version": "18.2.23",
+            "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.23.tgz",
+            "integrity": "sha512-ZQ71wgGOTmDYpnav2knkjr3qXdAFu0vsk8Ci5w3pGAIdj7/kKAyn+VsQDhXsmzzzepAiI9leWMmubXz690AI/A==",
             "dev": true,
             "dependencies": {
                 "@types/react": "*"
@@ -9322,9 +9321,9 @@
             }
         },
         "node_modules/@types/reactcss": {
-            "version": "1.2.11",
-            "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.11.tgz",
-            "integrity": "sha512-0fFy0ubuPlhksId8r9V8nsLcxBAPQnn15g/ERAElgE9L6rOquMj2CapsxqfyBuHlkp0/ndEUVnkYI7MkTtkGpw==",
+            "version": "1.2.12",
+            "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.12.tgz",
+            "integrity": "sha512-BrXUQ86/wbbFiZv8h/Q1/Q1XOsaHneYmCb/tHe9+M8XBAAUc2EHfdY0DY22ZZjVSaXr5ix7j+zsqO2eGZub8lQ==",
             "dev": true,
             "dependencies": {
                 "@types/react": "*"
@@ -9389,7 +9388,8 @@
         "node_modules/@types/scheduler": {
             "version": "0.16.8",
             "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
-            "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
+            "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
+            "optional": true
         },
         "node_modules/@types/send": {
             "version": "0.17.4",
@@ -9502,9 +9502,9 @@
             "dev": true
         },
         "node_modules/@types/web": {
-            "version": "0.0.138",
-            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.138.tgz",
-            "integrity": "sha512-oQD74hl+cNCZdSWIupJCXZ2azTuB3MJ/mrWlgYt+v4pD7/Dr78gl5hKAdieZNf9NrAqwUez79bHtnFVSNSscWA=="
+            "version": "0.0.142",
+            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.142.tgz",
+            "integrity": "sha512-QWDvMW+P3sdq8rhiw3dNyBR64O5adSY80gBjRd2xI3BADGsAFpAglblWucsGUjKVKyPm4EbYZkvFs7BRxPsBOg=="
         },
         "node_modules/@types/webidl-conversions": {
             "version": "7.0.3",
@@ -9562,60 +9562,60 @@
             "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
         },
         "node_modules/@vue/compiler-core": {
-            "version": "3.4.19",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
-            "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
+            "version": "3.4.21",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+            "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
             "dependencies": {
                 "@babel/parser": "^7.23.9",
-                "@vue/shared": "3.4.19",
+                "@vue/shared": "3.4.21",
                 "entities": "^4.5.0",
                 "estree-walker": "^2.0.2",
                 "source-map-js": "^1.0.2"
             }
         },
         "node_modules/@vue/compiler-dom": {
-            "version": "3.4.19",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
-            "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
+            "version": "3.4.21",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+            "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
             "dependencies": {
-                "@vue/compiler-core": "3.4.19",
-                "@vue/shared": "3.4.19"
+                "@vue/compiler-core": "3.4.21",
+                "@vue/shared": "3.4.21"
             }
         },
         "node_modules/@vue/compiler-sfc": {
-            "version": "3.4.19",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
-            "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
+            "version": "3.4.21",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+            "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
             "dependencies": {
                 "@babel/parser": "^7.23.9",
-                "@vue/compiler-core": "3.4.19",
-                "@vue/compiler-dom": "3.4.19",
-                "@vue/compiler-ssr": "3.4.19",
-                "@vue/shared": "3.4.19",
+                "@vue/compiler-core": "3.4.21",
+                "@vue/compiler-dom": "3.4.21",
+                "@vue/compiler-ssr": "3.4.21",
+                "@vue/shared": "3.4.21",
                 "estree-walker": "^2.0.2",
-                "magic-string": "^0.30.6",
-                "postcss": "^8.4.33",
+                "magic-string": "^0.30.7",
+                "postcss": "^8.4.35",
                 "source-map-js": "^1.0.2"
             }
         },
         "node_modules/@vue/compiler-ssr": {
-            "version": "3.4.19",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
-            "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
+            "version": "3.4.21",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+            "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
             "dependencies": {
-                "@vue/compiler-dom": "3.4.19",
-                "@vue/shared": "3.4.19"
+                "@vue/compiler-dom": "3.4.21",
+                "@vue/shared": "3.4.21"
             }
         },
         "node_modules/@vue/shared": {
-            "version": "3.4.19",
-            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
-            "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
+            "version": "3.4.21",
+            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
+            "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
         },
         "node_modules/@webassemblyjs/ast": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
-            "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
+            "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
             "dependencies": {
                 "@webassemblyjs/helper-numbers": "1.11.6",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@@ -9632,9 +9632,9 @@
             "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
         },
         "node_modules/@webassemblyjs/helper-buffer": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
-            "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA=="
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
+            "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw=="
         },
         "node_modules/@webassemblyjs/helper-numbers": {
             "version": "1.11.6",
@@ -9652,14 +9652,14 @@
             "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
         },
         "node_modules/@webassemblyjs/helper-wasm-section": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
-            "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
+            "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
-                "@webassemblyjs/helper-buffer": "1.11.6",
+                "@webassemblyjs/ast": "1.12.1",
+                "@webassemblyjs/helper-buffer": "1.12.1",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
-                "@webassemblyjs/wasm-gen": "1.11.6"
+                "@webassemblyjs/wasm-gen": "1.12.1"
             }
         },
         "node_modules/@webassemblyjs/ieee754": {
@@ -9684,26 +9684,26 @@
             "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
         },
         "node_modules/@webassemblyjs/wasm-edit": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
-            "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
+            "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
-                "@webassemblyjs/helper-buffer": "1.11.6",
+                "@webassemblyjs/ast": "1.12.1",
+                "@webassemblyjs/helper-buffer": "1.12.1",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
-                "@webassemblyjs/helper-wasm-section": "1.11.6",
-                "@webassemblyjs/wasm-gen": "1.11.6",
-                "@webassemblyjs/wasm-opt": "1.11.6",
-                "@webassemblyjs/wasm-parser": "1.11.6",
-                "@webassemblyjs/wast-printer": "1.11.6"
+                "@webassemblyjs/helper-wasm-section": "1.12.1",
+                "@webassemblyjs/wasm-gen": "1.12.1",
+                "@webassemblyjs/wasm-opt": "1.12.1",
+                "@webassemblyjs/wasm-parser": "1.12.1",
+                "@webassemblyjs/wast-printer": "1.12.1"
             }
         },
         "node_modules/@webassemblyjs/wasm-gen": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
-            "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
+            "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
+                "@webassemblyjs/ast": "1.12.1",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
                 "@webassemblyjs/ieee754": "1.11.6",
                 "@webassemblyjs/leb128": "1.11.6",
@@ -9711,22 +9711,22 @@
             }
         },
         "node_modules/@webassemblyjs/wasm-opt": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
-            "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
+            "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
-                "@webassemblyjs/helper-buffer": "1.11.6",
-                "@webassemblyjs/wasm-gen": "1.11.6",
-                "@webassemblyjs/wasm-parser": "1.11.6"
+                "@webassemblyjs/ast": "1.12.1",
+                "@webassemblyjs/helper-buffer": "1.12.1",
+                "@webassemblyjs/wasm-gen": "1.12.1",
+                "@webassemblyjs/wasm-parser": "1.12.1"
             }
         },
         "node_modules/@webassemblyjs/wasm-parser": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
-            "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
+            "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
+                "@webassemblyjs/ast": "1.12.1",
                 "@webassemblyjs/helper-api-error": "1.11.6",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
                 "@webassemblyjs/ieee754": "1.11.6",
@@ -9735,11 +9735,11 @@
             }
         },
         "node_modules/@webassemblyjs/wast-printer": {
-            "version": "1.11.6",
-            "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
-            "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
+            "version": "1.12.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
+            "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
             "dependencies": {
-                "@webassemblyjs/ast": "1.11.6",
+                "@webassemblyjs/ast": "1.12.1",
                 "@xtuc/long": "4.2.2"
             }
         },
@@ -9878,9 +9878,9 @@
             }
         },
         "node_modules/adm-zip": {
-            "version": "0.5.10",
-            "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
-            "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
+            "version": "0.5.12",
+            "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz",
+            "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==",
             "engines": {
                 "node": ">=6.0"
             }
@@ -10075,9 +10075,9 @@
             "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
         },
         "node_modules/archiver": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz",
-            "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==",
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.2.tgz",
+            "integrity": "sha512-UQ/2nW7NMl1G+1UnrLypQw1VdT9XZg/ECcKPq7l+STzStrSivFIXIp34D8M5zeNGW5NoOupdYCHv6VySCPNNlw==",
             "dependencies": {
                 "archiver-utils": "^4.0.1",
                 "async": "^3.2.4",
@@ -10191,15 +10191,16 @@
             "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
         },
         "node_modules/array-includes": {
-            "version": "3.1.7",
-            "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
-            "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
+            "version": "3.1.8",
+            "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
+            "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1",
-                "get-intrinsic": "^1.2.1",
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.2",
+                "es-object-atoms": "^1.0.0",
+                "get-intrinsic": "^1.2.4",
                 "is-string": "^1.0.7"
             },
             "engines": {
@@ -10217,17 +10218,18 @@
                 "node": ">=8"
             }
         },
-        "node_modules/array.prototype.filter": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
-            "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==",
+        "node_modules/array.prototype.findlast": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+            "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1",
-                "es-array-method-boxes-properly": "^1.0.0",
-                "is-string": "^1.0.7"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.2",
+                "es-errors": "^1.3.0",
+                "es-object-atoms": "^1.0.0",
+                "es-shim-unscopables": "^1.0.2"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -10237,15 +10239,16 @@
             }
         },
         "node_modules/array.prototype.findlastindex": {
-            "version": "1.2.4",
-            "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz",
-            "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==",
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
+            "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.5",
+                "call-bind": "^1.0.7",
                 "define-properties": "^1.2.1",
-                "es-abstract": "^1.22.3",
+                "es-abstract": "^1.23.2",
                 "es-errors": "^1.3.0",
+                "es-object-atoms": "^1.0.0",
                 "es-shim-unscopables": "^1.0.2"
             },
             "engines": {
@@ -10291,6 +10294,18 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/array.prototype.toreversed": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz",
+            "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.2.0",
+                "es-abstract": "^1.22.1",
+                "es-shim-unscopables": "^1.0.0"
+            }
+        },
         "node_modules/array.prototype.tosorted": {
             "version": "1.1.3",
             "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz",
@@ -10408,15 +10423,6 @@
             "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
             "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
         },
-        "node_modules/asynciterator.prototype": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
-            "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==",
-            "dev": true,
-            "dependencies": {
-                "has-symbols": "^1.0.3"
-            }
-        },
         "node_modules/asynckit": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -10459,11 +10465,11 @@
             }
         },
         "node_modules/axios": {
-            "version": "1.6.7",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
-            "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+            "version": "1.6.8",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+            "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
             "dependencies": {
-                "follow-redirects": "^1.15.4",
+                "follow-redirects": "^1.15.6",
                 "form-data": "^4.0.0",
                 "proxy-from-env": "^1.1.0"
             }
@@ -10554,12 +10560,12 @@
             }
         },
         "node_modules/babel-plugin-polyfill-corejs2": {
-            "version": "0.4.8",
-            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz",
-            "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==",
+            "version": "0.4.10",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz",
+            "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==",
             "dependencies": {
                 "@babel/compat-data": "^7.22.6",
-                "@babel/helper-define-polyfill-provider": "^0.5.0",
+                "@babel/helper-define-polyfill-provider": "^0.6.1",
                 "semver": "^6.3.1"
             },
             "peerDependencies": {
@@ -10567,23 +10573,23 @@
             }
         },
         "node_modules/babel-plugin-polyfill-corejs3": {
-            "version": "0.9.0",
-            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz",
-            "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==",
+            "version": "0.10.4",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz",
+            "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==",
             "dependencies": {
-                "@babel/helper-define-polyfill-provider": "^0.5.0",
-                "core-js-compat": "^3.34.0"
+                "@babel/helper-define-polyfill-provider": "^0.6.1",
+                "core-js-compat": "^3.36.1"
             },
             "peerDependencies": {
                 "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
             }
         },
         "node_modules/babel-plugin-polyfill-regenerator": {
-            "version": "0.5.5",
-            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz",
-            "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==",
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz",
+            "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==",
             "dependencies": {
-                "@babel/helper-define-polyfill-provider": "^0.5.0"
+                "@babel/helper-define-polyfill-provider": "^0.6.1"
             },
             "peerDependencies": {
                 "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -10636,9 +10642,9 @@
             "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
         },
         "node_modules/bare-events": {
-            "version": "2.2.0",
-            "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz",
-            "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==",
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
+            "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
             "optional": true
         },
         "node_modules/base-64": {
@@ -10742,11 +10748,14 @@
             }
         },
         "node_modules/binary-extensions": {
-            "version": "2.2.0",
-            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
-            "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+            "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
             "engines": {
                 "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
             }
         },
         "node_modules/bingmaps-react": {
@@ -10886,9 +10895,9 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm": {
-            "version": "9.9.2",
-            "resolved": "https://registry.npmjs.org/npm/-/npm-9.9.2.tgz",
-            "integrity": "sha512-D3tV+W0PzJOlwo8YmO6fNzaB1CrMVYd1V+2TURF6lbCbmZKqMsYgeQfPVvqiM3zbNSJPhFEnmlEXIogH2Vq7PQ==",
+            "version": "9.9.3",
+            "resolved": "https://registry.npmjs.org/npm/-/npm-9.9.3.tgz",
+            "integrity": "sha512-Z1l+rcQ5kYb17F3hHtO601arEpvdRYnCLtg8xo3AGtyj3IthwaraEOexI9903uANkifFbqHC8hT53KIrozWg8A==",
             "bundleDependencies": [
                 "@isaacs/string-locale-compare",
                 "@npmcli/arborist",
@@ -10972,21 +10981,21 @@
                 "@npmcli/run-script": "^6.0.2",
                 "abbrev": "^2.0.0",
                 "archy": "~1.0.0",
-                "cacache": "^17.1.3",
+                "cacache": "^17.1.4",
                 "chalk": "^5.3.0",
-                "ci-info": "^3.8.0",
+                "ci-info": "^4.0.0",
                 "cli-columns": "^4.0.0",
                 "cli-table3": "^0.6.3",
                 "columnify": "^1.6.0",
                 "fastest-levenshtein": "^1.0.16",
-                "fs-minipass": "^3.0.2",
-                "glob": "^10.2.7",
+                "fs-minipass": "^3.0.3",
+                "glob": "^10.3.10",
                 "graceful-fs": "^4.2.11",
                 "hosted-git-info": "^6.1.1",
                 "ini": "^4.1.1",
                 "init-package-json": "^5.0.0",
                 "is-cidr": "^4.0.2",
-                "json-parse-even-better-errors": "^3.0.0",
+                "json-parse-even-better-errors": "^3.0.1",
                 "libnpmaccess": "^7.0.2",
                 "libnpmdiff": "^5.0.20",
                 "libnpmexec": "^6.0.4",
@@ -11000,14 +11009,14 @@
                 "libnpmversion": "^4.0.2",
                 "make-fetch-happen": "^11.1.1",
                 "minimatch": "^9.0.3",
-                "minipass": "^5.0.0",
+                "minipass": "^7.0.4",
                 "minipass-pipeline": "^1.2.4",
                 "ms": "^2.1.2",
-                "node-gyp": "^9.4.0",
+                "node-gyp": "^9.4.1",
                 "nopt": "^7.2.0",
                 "normalize-package-data": "^5.0.0",
                 "npm-audit-report": "^5.0.0",
-                "npm-install-checks": "^6.2.0",
+                "npm-install-checks": "^6.3.0",
                 "npm-package-arg": "^10.1.0",
                 "npm-pick-manifest": "^8.0.2",
                 "npm-profile": "^7.0.1",
@@ -11020,12 +11029,12 @@
                 "proc-log": "^3.0.0",
                 "qrcode-terminal": "^0.12.0",
                 "read": "^2.1.0",
-                "semver": "^7.5.4",
+                "semver": "^7.6.0",
                 "sigstore": "^1.9.0",
                 "spdx-expression-parse": "^3.0.1",
-                "ssri": "^10.0.4",
+                "ssri": "^10.0.5",
                 "supports-color": "^9.4.0",
-                "tar": "^6.1.15",
+                "tar": "^6.2.0",
                 "text-table": "~0.2.0",
                 "tiny-relative-date": "^1.3.0",
                 "treeverse": "^3.0.0",
@@ -11050,6 +11059,11 @@
                 "node": ">=0.1.90"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/@gar/promisify": {
+            "version": "1.1.3",
+            "inBundle": true,
+            "license": "MIT"
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/@isaacs/cliui": {
             "version": "8.0.2",
             "inBundle": true,
@@ -11118,7 +11132,7 @@
             "license": "ISC"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/@npmcli/arborist": {
-            "version": "6.5.0",
+            "version": "6.5.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -11130,7 +11144,7 @@
                 "@npmcli/name-from-folder": "^2.0.0",
                 "@npmcli/node-gyp": "^3.0.0",
                 "@npmcli/package-json": "^4.0.0",
-                "@npmcli/query": "^3.0.0",
+                "@npmcli/query": "^3.1.0",
                 "@npmcli/run-script": "^6.0.0",
                 "bin-links": "^4.0.1",
                 "cacache": "^17.0.4",
@@ -11164,12 +11178,12 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/@npmcli/config": {
-            "version": "6.4.0",
+            "version": "6.4.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/map-workspaces": "^3.0.2",
-                "ci-info": "^3.8.0",
+                "ci-info": "^4.0.0",
                 "ini": "^4.1.0",
                 "nopt": "^7.0.0",
                 "proc-log": "^3.0.0",
@@ -11264,6 +11278,18 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/@npmcli/move-file": {
+            "version": "2.0.1",
+            "inBundle": true,
+            "license": "MIT",
+            "dependencies": {
+                "mkdirp": "^1.0.4",
+                "rimraf": "^3.0.2"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/@npmcli/name-from-folder": {
             "version": "2.0.0",
             "inBundle": true,
@@ -11309,7 +11335,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/@npmcli/query": {
-            "version": "3.0.0",
+            "version": "3.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -11423,17 +11449,6 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/abort-controller": {
-            "version": "3.0.0",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "event-target-shim": "^5.0.0"
-            },
-            "engines": {
-                "node": ">=6.5"
-            }
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/agent-base": {
             "version": "6.0.2",
             "inBundle": true,
@@ -11446,12 +11461,10 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/agentkeepalive": {
-            "version": "4.3.0",
+            "version": "4.5.0",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "debug": "^4.1.0",
-                "depd": "^2.0.0",
                 "humanize-ms": "^1.2.1"
             },
             "engines": {
@@ -11503,13 +11516,9 @@
             "license": "MIT"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/are-we-there-yet": {
-            "version": "4.0.0",
+            "version": "4.0.2",
             "inBundle": true,
             "license": "ISC",
-            "dependencies": {
-                "delegates": "^1.0.0",
-                "readable-stream": "^4.1.0"
-            },
             "engines": {
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
@@ -11519,27 +11528,8 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/base64-js": {
-            "version": "1.5.1",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "inBundle": true,
-            "license": "MIT"
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/bin-links": {
-            "version": "4.0.2",
+            "version": "4.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -11568,29 +11558,6 @@
                 "balanced-match": "^1.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/buffer": {
-            "version": "6.0.3",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "base64-js": "^1.3.1",
-                "ieee754": "^1.2.1"
-            }
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/builtins": {
             "version": "5.0.1",
             "inBundle": true,
@@ -11600,7 +11567,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/cacache": {
-            "version": "17.1.3",
+            "version": "17.1.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -11608,7 +11575,7 @@
                 "fs-minipass": "^3.0.0",
                 "glob": "^10.2.2",
                 "lru-cache": "^7.7.1",
-                "minipass": "^5.0.0",
+                "minipass": "^7.0.3",
                 "minipass-collect": "^1.0.2",
                 "minipass-flush": "^1.0.5",
                 "minipass-pipeline": "^1.2.4",
@@ -11641,7 +11608,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/ci-info": {
-            "version": "3.8.0",
+            "version": "4.0.0",
             "funding": [
                 {
                     "type": "github",
@@ -11708,7 +11675,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/cmd-shim": {
-            "version": "6.0.1",
+            "version": "6.0.2",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -11841,16 +11808,8 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/depd": {
-            "version": "2.0.0",
-            "inBundle": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/diff": {
-            "version": "5.1.0",
+            "version": "5.2.0",
             "inBundle": true,
             "license": "BSD-3-Clause",
             "engines": {
@@ -11889,22 +11848,6 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/event-target-shim": {
-            "version": "5.0.1",
-            "inBundle": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/browndash-components/node_modules/npm/node_modules/events": {
-            "version": "3.3.0",
-            "inBundle": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.8.x"
-            }
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/exponential-backoff": {
             "version": "3.1.1",
             "inBundle": true,
@@ -11934,11 +11877,11 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/fs-minipass": {
-            "version": "3.0.2",
+            "version": "3.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "minipass": "^5.0.0"
+                "minipass": "^7.0.3"
             },
             "engines": {
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
@@ -11950,9 +11893,12 @@
             "license": "ISC"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/function-bind": {
-            "version": "1.1.1",
+            "version": "1.1.2",
             "inBundle": true,
-            "license": "MIT"
+            "license": "MIT",
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/gauge": {
             "version": "5.0.1",
@@ -11973,18 +11919,18 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/glob": {
-            "version": "10.2.7",
+            "version": "10.3.10",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "foreground-child": "^3.1.0",
-                "jackspeak": "^2.0.3",
+                "jackspeak": "^2.3.5",
                 "minimatch": "^9.0.1",
-                "minipass": "^5.0.0 || ^6.0.2",
-                "path-scurry": "^1.7.0"
+                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                "path-scurry": "^1.10.1"
             },
             "bin": {
-                "glob": "dist/cjs/src/bin.js"
+                "glob": "dist/esm/bin.mjs"
             },
             "engines": {
                 "node": ">=16 || 14 >=14.17"
@@ -11998,22 +11944,22 @@
             "inBundle": true,
             "license": "ISC"
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/has": {
-            "version": "1.0.3",
+        "node_modules/browndash-components/node_modules/npm/node_modules/has-unicode": {
+            "version": "2.0.1",
+            "inBundle": true,
+            "license": "ISC"
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/hasown": {
+            "version": "2.0.1",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "function-bind": "^1.1.1"
+                "function-bind": "^1.1.2"
             },
             "engines": {
-                "node": ">= 0.4.0"
+                "node": ">= 0.4"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/has-unicode": {
-            "version": "2.0.1",
-            "inBundle": true,
-            "license": "ISC"
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/hosted-git-info": {
             "version": "6.1.1",
             "inBundle": true,
@@ -12075,27 +12021,8 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/ieee754": {
-            "version": "1.2.1",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "inBundle": true,
-            "license": "BSD-3-Clause"
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/ignore-walk": {
-            "version": "6.0.3",
+            "version": "6.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12121,6 +12048,11 @@
                 "node": ">=8"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/infer-owner": {
+            "version": "1.0.4",
+            "inBundle": true,
+            "license": "ISC"
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/inflight": {
             "version": "1.0.6",
             "inBundle": true,
@@ -12160,10 +12092,22 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/ip": {
-            "version": "2.0.0",
+        "node_modules/browndash-components/node_modules/npm/node_modules/ip-address": {
+            "version": "9.0.5",
             "inBundle": true,
-            "license": "MIT"
+            "license": "MIT",
+            "dependencies": {
+                "jsbn": "1.1.0",
+                "sprintf-js": "^1.1.3"
+            },
+            "engines": {
+                "node": ">= 12"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": {
+            "version": "1.1.3",
+            "inBundle": true,
+            "license": "BSD-3-Clause"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/ip-regex": {
             "version": "4.3.0",
@@ -12185,11 +12129,11 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/is-core-module": {
-            "version": "2.13.0",
+            "version": "2.13.1",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "has": "^1.0.3"
+                "hasown": "^2.0.0"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -12214,7 +12158,7 @@
             "license": "ISC"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/jackspeak": {
-            "version": "2.2.1",
+            "version": "2.3.6",
             "inBundle": true,
             "license": "BlueOak-1.0.0",
             "dependencies": {
@@ -12230,8 +12174,13 @@
                 "@pkgjs/parseargs": "^0.11.0"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/jsbn": {
+            "version": "1.1.0",
+            "inBundle": true,
+            "license": "MIT"
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/json-parse-even-better-errors": {
-            "version": "3.0.0",
+            "version": "3.0.1",
             "inBundle": true,
             "license": "MIT",
             "engines": {
@@ -12265,7 +12214,7 @@
             "license": "MIT"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmaccess": {
-            "version": "7.0.2",
+            "version": "7.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12277,7 +12226,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmdiff": {
-            "version": "5.0.20",
+            "version": "5.0.21",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12296,13 +12245,13 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmexec": {
-            "version": "6.0.4",
+            "version": "6.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/arborist": "^6.5.0",
                 "@npmcli/run-script": "^6.0.0",
-                "ci-info": "^3.7.1",
+                "ci-info": "^4.0.0",
                 "npm-package-arg": "^10.1.0",
                 "npmlog": "^7.0.1",
                 "pacote": "^15.0.8",
@@ -12317,7 +12266,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmfund": {
-            "version": "4.2.1",
+            "version": "4.2.2",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12328,7 +12277,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmhook": {
-            "version": "9.0.3",
+            "version": "9.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12340,7 +12289,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmorg": {
-            "version": "5.0.4",
+            "version": "5.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12352,7 +12301,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmpack": {
-            "version": "5.0.20",
+            "version": "5.0.21",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12366,11 +12315,11 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmpublish": {
-            "version": "7.5.1",
+            "version": "7.5.2",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "ci-info": "^3.6.1",
+                "ci-info": "^4.0.0",
                 "normalize-package-data": "^5.0.0",
                 "npm-package-arg": "^10.1.0",
                 "npm-registry-fetch": "^14.0.3",
@@ -12384,7 +12333,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmsearch": {
-            "version": "6.0.2",
+            "version": "6.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12395,7 +12344,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmteam": {
-            "version": "5.0.3",
+            "version": "5.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12407,7 +12356,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/libnpmversion": {
-            "version": "4.0.2",
+            "version": "4.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -12454,6 +12403,14 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/make-fetch-happen/node_modules/minipass": {
+            "version": "5.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/minimatch": {
             "version": "9.0.3",
             "inBundle": true,
@@ -12469,11 +12426,11 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/minipass": {
-            "version": "5.0.0",
+            "version": "7.0.4",
             "inBundle": true,
             "license": "ISC",
             "engines": {
-                "node": ">=8"
+                "node": ">=16 || 14 >=14.17"
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/minipass-collect": {
@@ -12499,11 +12456,11 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/minipass-fetch": {
-            "version": "3.0.3",
+            "version": "3.0.4",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "minipass": "^5.0.0",
+                "minipass": "^7.0.3",
                 "minipass-sized": "^1.0.3",
                 "minizlib": "^2.1.2"
             },
@@ -12656,7 +12613,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp": {
-            "version": "9.4.0",
+            "version": "9.4.1",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
@@ -12664,7 +12621,7 @@
                 "exponential-backoff": "^3.1.1",
                 "glob": "^7.1.4",
                 "graceful-fs": "^4.2.6",
-                "make-fetch-happen": "^11.0.3",
+                "make-fetch-happen": "^10.0.3",
                 "nopt": "^6.0.0",
                 "npmlog": "^6.0.0",
                 "rimraf": "^3.0.2",
@@ -12679,6 +12636,18 @@
                 "node": "^12.13 || ^14.13 || >=16"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/@npmcli/fs": {
+            "version": "2.1.2",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "@gar/promisify": "^1.1.3",
+                "semver": "^7.3.5"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/abbrev": {
             "version": "1.1.1",
             "inBundle": true,
@@ -12705,6 +12674,82 @@
                 "concat-map": "0.0.1"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/cacache": {
+            "version": "16.1.3",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "@npmcli/fs": "^2.1.0",
+                "@npmcli/move-file": "^2.0.0",
+                "chownr": "^2.0.0",
+                "fs-minipass": "^2.1.0",
+                "glob": "^8.0.1",
+                "infer-owner": "^1.0.4",
+                "lru-cache": "^7.7.1",
+                "minipass": "^3.1.6",
+                "minipass-collect": "^1.0.2",
+                "minipass-flush": "^1.0.5",
+                "minipass-pipeline": "^1.2.4",
+                "mkdirp": "^1.0.4",
+                "p-map": "^4.0.0",
+                "promise-inflight": "^1.0.1",
+                "rimraf": "^3.0.2",
+                "ssri": "^9.0.0",
+                "tar": "^6.1.11",
+                "unique-filename": "^2.0.0"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/brace-expansion": {
+            "version": "2.0.1",
+            "inBundle": true,
+            "license": "MIT",
+            "dependencies": {
+                "balanced-match": "^1.0.0"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/glob": {
+            "version": "8.1.0",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^5.0.1",
+                "once": "^1.3.0"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/minimatch": {
+            "version": "5.1.6",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/fs-minipass": {
+            "version": "2.1.0",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "minipass": "^3.0.0"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/gauge": {
             "version": "4.0.4",
             "inBundle": true,
@@ -12742,6 +12787,32 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/make-fetch-happen": {
+            "version": "10.2.1",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "agentkeepalive": "^4.2.1",
+                "cacache": "^16.1.0",
+                "http-cache-semantics": "^4.1.0",
+                "http-proxy-agent": "^5.0.0",
+                "https-proxy-agent": "^5.0.0",
+                "is-lambda": "^1.0.1",
+                "lru-cache": "^7.7.1",
+                "minipass": "^3.1.6",
+                "minipass-collect": "^1.0.2",
+                "minipass-fetch": "^2.0.3",
+                "minipass-flush": "^1.0.5",
+                "minipass-pipeline": "^1.2.4",
+                "negotiator": "^0.6.3",
+                "promise-retry": "^2.0.1",
+                "socks-proxy-agent": "^7.0.0",
+                "ssri": "^9.0.0"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/minimatch": {
             "version": "3.1.2",
             "inBundle": true,
@@ -12753,6 +12824,33 @@
                 "node": "*"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/minipass": {
+            "version": "3.3.6",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/minipass-fetch": {
+            "version": "2.1.2",
+            "inBundle": true,
+            "license": "MIT",
+            "dependencies": {
+                "minipass": "^3.1.6",
+                "minipass-sized": "^1.0.3",
+                "minizlib": "^2.1.2"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            },
+            "optionalDependencies": {
+                "encoding": "^0.1.13"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/nopt": {
             "version": "6.0.0",
             "inBundle": true,
@@ -12781,23 +12879,43 @@
                 "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/readable-stream": {
-            "version": "3.6.2",
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": {
+            "version": "3.0.7",
             "inBundle": true,
-            "license": "MIT",
+            "license": "ISC"
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/ssri": {
+            "version": "9.0.1",
+            "inBundle": true,
+            "license": "ISC",
             "dependencies": {
-                "inherits": "^2.0.3",
-                "string_decoder": "^1.1.1",
-                "util-deprecate": "^1.0.1"
+                "minipass": "^3.1.1"
             },
             "engines": {
-                "node": ">= 6"
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": {
-            "version": "3.0.7",
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/unique-filename": {
+            "version": "2.0.1",
             "inBundle": true,
-            "license": "ISC"
+            "license": "ISC",
+            "dependencies": {
+                "unique-slug": "^3.0.0"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
+        },
+        "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/unique-slug": {
+            "version": "3.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "dependencies": {
+                "imurmurhash": "^0.1.4"
+            },
+            "engines": {
+                "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+            }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/node-gyp/node_modules/which": {
             "version": "2.0.2",
@@ -12861,7 +12979,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/npm-install-checks": {
-            "version": "6.2.0",
+            "version": "6.3.0",
             "inBundle": true,
             "license": "BSD-2-Clause",
             "dependencies": {
@@ -12947,6 +13065,14 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/npm-registry-fetch/node_modules/minipass": {
+            "version": "5.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/npm-user-validate": {
             "version": "2.0.0",
             "inBundle": true,
@@ -13022,6 +13148,14 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/pacote/node_modules/minipass": {
+            "version": "5.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/parse-conflict-json": {
             "version": "3.0.1",
             "inBundle": true,
@@ -13052,12 +13186,12 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/path-scurry": {
-            "version": "1.9.2",
+            "version": "1.10.1",
             "inBundle": true,
             "license": "BlueOak-1.0.0",
             "dependencies": {
-                "lru-cache": "^9.1.1",
-                "minipass": "^5.0.0 || ^6.0.2"
+                "lru-cache": "^9.1.1 || ^10.0.0",
+                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
             },
             "engines": {
                 "node": ">=16 || 14 >=14.17"
@@ -13067,7 +13201,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/path-scurry/node_modules/lru-cache": {
-            "version": "9.1.1",
+            "version": "10.2.0",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -13075,7 +13209,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/postcss-selector-parser": {
-            "version": "6.0.13",
+            "version": "6.0.15",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
@@ -13094,14 +13228,6 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
-        "node_modules/browndash-components/node_modules/npm/node_modules/process": {
-            "version": "0.11.10",
-            "inBundle": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6.0"
-            }
-        },
         "node_modules/browndash-components/node_modules/npm/node_modules/promise-all-reject-late": {
             "version": "1.0.1",
             "inBundle": true,
@@ -13199,17 +13325,16 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/readable-stream": {
-            "version": "4.4.0",
+            "version": "3.6.2",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "abort-controller": "^3.0.0",
-                "buffer": "^6.0.3",
-                "events": "^3.3.0",
-                "process": "^0.11.10"
+                "inherits": "^2.0.3",
+                "string_decoder": "^1.1.1",
+                "util-deprecate": "^1.0.1"
             },
             "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+                "node": ">= 6"
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/retry": {
@@ -13299,7 +13424,7 @@
             "optional": true
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/semver": {
-            "version": "7.5.4",
+            "version": "7.6.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -13348,7 +13473,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/signal-exit": {
-            "version": "4.0.2",
+            "version": "4.1.0",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -13386,15 +13511,15 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/socks": {
-            "version": "2.7.1",
+            "version": "2.8.1",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "ip": "^2.0.0",
+                "ip-address": "^9.0.5",
                 "smart-buffer": "^4.2.0"
             },
             "engines": {
-                "node": ">= 10.13.0",
+                "node": ">= 10.0.0",
                 "npm": ">= 3.0.0"
             }
         },
@@ -13421,7 +13546,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/spdx-exceptions": {
-            "version": "2.3.0",
+            "version": "2.5.0",
             "inBundle": true,
             "license": "CC-BY-3.0"
         },
@@ -13435,16 +13560,16 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/spdx-license-ids": {
-            "version": "3.0.13",
+            "version": "3.0.17",
             "inBundle": true,
             "license": "CC0-1.0"
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/ssri": {
-            "version": "10.0.4",
+            "version": "10.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "minipass": "^5.0.0"
+                "minipass": "^7.0.3"
             },
             "engines": {
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
@@ -13520,7 +13645,7 @@
             }
         },
         "node_modules/browndash-components/node_modules/npm/node_modules/tar": {
-            "version": "6.1.15",
+            "version": "6.2.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -13557,6 +13682,14 @@
                 "node": ">=8"
             }
         },
+        "node_modules/browndash-components/node_modules/npm/node_modules/tar/node_modules/minipass": {
+            "version": "5.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/browndash-components/node_modules/npm/node_modules/text-table": {
             "version": "0.2.0",
             "inBundle": true,
@@ -13833,9 +13966,9 @@
             }
         },
         "node_modules/bson": {
-            "version": "6.3.0",
-            "resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz",
-            "integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==",
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz",
+            "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==",
             "engines": {
                 "node": ">=16.20.1"
             }
@@ -14029,9 +14162,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001589",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
-            "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
+            "version": "1.0.30001600",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
+            "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -14171,14 +14304,14 @@
             }
         },
         "node_modules/chart.js": {
-            "version": "4.4.1",
-            "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
-            "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
+            "version": "4.4.2",
+            "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz",
+            "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==",
             "dependencies": {
                 "@kurkle/color": "^0.3.0"
             },
             "engines": {
-                "pnpm": ">=7"
+                "pnpm": ">=8"
             }
         },
         "node_modules/cheap-ruler": {
@@ -14519,9 +14652,9 @@
             "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
         },
         "node_modules/compress-commons": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz",
-            "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==",
+            "version": "5.0.3",
+            "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.3.tgz",
+            "integrity": "sha512-/UIcLWvwAQyVibgpQDPtfNM3SvqN7G9elAPAV7GM0L53EbNWwWiCsWtK8Fwed/APEbptPHXs5PuW+y8Bq8lFTA==",
             "dependencies": {
                 "crc-32": "^1.2.0",
                 "crc32-stream": "^5.0.0",
@@ -14754,9 +14887,9 @@
             }
         },
         "node_modules/core-js": {
-            "version": "3.36.0",
-            "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz",
-            "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==",
+            "version": "3.36.1",
+            "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
+            "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
             "hasInstallScript": true,
             "funding": {
                 "type": "opencollective",
@@ -14764,11 +14897,11 @@
             }
         },
         "node_modules/core-js-compat": {
-            "version": "3.36.0",
-            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
-            "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
+            "version": "3.36.1",
+            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
+            "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
             "dependencies": {
-                "browserslist": "^4.22.3"
+                "browserslist": "^4.23.0"
             },
             "funding": {
                 "type": "opencollective",
@@ -14776,9 +14909,9 @@
             }
         },
         "node_modules/core-js-pure": {
-            "version": "3.36.0",
-            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz",
-            "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==",
+            "version": "3.36.1",
+            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz",
+            "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==",
             "hasInstallScript": true,
             "funding": {
                 "type": "opencollective",
@@ -14829,9 +14962,9 @@
             }
         },
         "node_modules/crc32-stream": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz",
-            "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==",
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.1.tgz",
+            "integrity": "sha512-lO1dFui+CEUh/ztYIpgpKItKW9Bb4NWakCRJrnqAbFIYD+OZAwb2VfD5T5eXMw2FNcsDHkQcNl/Wh3iVXYwU6g==",
             "dependencies": {
                 "crc-32": "^1.2.0",
                 "readable-stream": "^3.4.0"
@@ -15097,9 +15230,9 @@
             }
         },
         "node_modules/csv-stringify": {
-            "version": "6.4.5",
-            "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
-            "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
+            "version": "6.4.6",
+            "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz",
+            "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag=="
         },
         "node_modules/custom-event": {
             "version": "1.0.1",
@@ -15113,9 +15246,9 @@
             "deprecated": "Package no longer supported. Contact support@npmjs.com for more info."
         },
         "node_modules/d3": {
-            "version": "7.8.5",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
-            "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==",
+            "version": "7.9.0",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+            "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
             "dependencies": {
                 "d3-array": "3",
                 "d3-axis": "3",
@@ -15402,9 +15535,9 @@
             }
         },
         "node_modules/d3-scale-chromatic": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
-            "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==",
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+            "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
             "dependencies": {
                 "d3-color": "1 - 3",
                 "d3-interpolate": "1 - 3"
@@ -15501,9 +15634,9 @@
             }
         },
         "node_modules/d3/node_modules/d3-geo": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
-            "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+            "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
             "dependencies": {
                 "d3-array": "2.5.0 - 3"
             },
@@ -15549,6 +15682,57 @@
                 "node": ">=18"
             }
         },
+        "node_modules/data-view-buffer": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
+            "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.6",
+                "es-errors": "^1.3.0",
+                "is-data-view": "^1.0.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/data-view-byte-length": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz",
+            "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "es-errors": "^1.3.0",
+                "is-data-view": "^1.0.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/data-view-byte-offset": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz",
+            "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.6",
+                "es-errors": "^1.3.0",
+                "is-data-view": "^1.0.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/date-fns": {
             "version": "2.30.0",
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -15901,9 +16085,9 @@
             }
         },
         "node_modules/detect-libc": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
-            "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+            "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
             "engines": {
                 "node": ">=8"
             }
@@ -16123,9 +16307,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         "node_modules/electron-to-chromium": {
-            "version": "1.4.680",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz",
-            "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A=="
+            "version": "1.4.722",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.722.tgz",
+            "integrity": "sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ=="
         },
         "node_modules/emoji-regex": {
             "version": "9.2.2",
@@ -16229,9 +16413,9 @@
             }
         },
         "node_modules/enhanced-resolve": {
-            "version": "5.15.0",
-            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
-            "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+            "version": "5.16.0",
+            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
+            "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
             "dependencies": {
                 "graceful-fs": "^4.2.4",
                 "tapable": "^2.2.0"
@@ -16271,18 +16455,22 @@
             }
         },
         "node_modules/es-abstract": {
-            "version": "1.22.4",
-            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz",
-            "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==",
+            "version": "1.23.3",
+            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
+            "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==",
             "dev": true,
             "dependencies": {
                 "array-buffer-byte-length": "^1.0.1",
                 "arraybuffer.prototype.slice": "^1.0.3",
-                "available-typed-arrays": "^1.0.6",
+                "available-typed-arrays": "^1.0.7",
                 "call-bind": "^1.0.7",
+                "data-view-buffer": "^1.0.1",
+                "data-view-byte-length": "^1.0.1",
+                "data-view-byte-offset": "^1.0.0",
                 "es-define-property": "^1.0.0",
                 "es-errors": "^1.3.0",
-                "es-set-tostringtag": "^2.0.2",
+                "es-object-atoms": "^1.0.0",
+                "es-set-tostringtag": "^2.0.3",
                 "es-to-primitive": "^1.2.1",
                 "function.prototype.name": "^1.1.6",
                 "get-intrinsic": "^1.2.4",
@@ -16290,15 +16478,16 @@
                 "globalthis": "^1.0.3",
                 "gopd": "^1.0.1",
                 "has-property-descriptors": "^1.0.2",
-                "has-proto": "^1.0.1",
+                "has-proto": "^1.0.3",
                 "has-symbols": "^1.0.3",
-                "hasown": "^2.0.1",
+                "hasown": "^2.0.2",
                 "internal-slot": "^1.0.7",
                 "is-array-buffer": "^3.0.4",
                 "is-callable": "^1.2.7",
-                "is-negative-zero": "^2.0.2",
+                "is-data-view": "^1.0.1",
+                "is-negative-zero": "^2.0.3",
                 "is-regex": "^1.1.4",
-                "is-shared-array-buffer": "^1.0.2",
+                "is-shared-array-buffer": "^1.0.3",
                 "is-string": "^1.0.7",
                 "is-typed-array": "^1.1.13",
                 "is-weakref": "^1.0.2",
@@ -16306,17 +16495,17 @@
                 "object-keys": "^1.1.1",
                 "object.assign": "^4.1.5",
                 "regexp.prototype.flags": "^1.5.2",
-                "safe-array-concat": "^1.1.0",
+                "safe-array-concat": "^1.1.2",
                 "safe-regex-test": "^1.0.3",
-                "string.prototype.trim": "^1.2.8",
-                "string.prototype.trimend": "^1.0.7",
-                "string.prototype.trimstart": "^1.0.7",
-                "typed-array-buffer": "^1.0.1",
-                "typed-array-byte-length": "^1.0.0",
-                "typed-array-byte-offset": "^1.0.0",
-                "typed-array-length": "^1.0.4",
+                "string.prototype.trim": "^1.2.9",
+                "string.prototype.trimend": "^1.0.8",
+                "string.prototype.trimstart": "^1.0.8",
+                "typed-array-buffer": "^1.0.2",
+                "typed-array-byte-length": "^1.0.1",
+                "typed-array-byte-offset": "^1.0.2",
+                "typed-array-length": "^1.0.6",
                 "unbox-primitive": "^1.0.2",
-                "which-typed-array": "^1.1.14"
+                "which-typed-array": "^1.1.15"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -16325,12 +16514,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/es-array-method-boxes-properly": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
-            "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
-            "dev": true
-        },
         "node_modules/es-define-property": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@@ -16351,35 +16534,46 @@
             }
         },
         "node_modules/es-iterator-helpers": {
-            "version": "1.0.17",
-            "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz",
-            "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==",
+            "version": "1.0.18",
+            "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz",
+            "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==",
             "dev": true,
             "dependencies": {
-                "asynciterator.prototype": "^1.0.0",
                 "call-bind": "^1.0.7",
                 "define-properties": "^1.2.1",
-                "es-abstract": "^1.22.4",
+                "es-abstract": "^1.23.0",
                 "es-errors": "^1.3.0",
-                "es-set-tostringtag": "^2.0.2",
+                "es-set-tostringtag": "^2.0.3",
                 "function-bind": "^1.1.2",
                 "get-intrinsic": "^1.2.4",
                 "globalthis": "^1.0.3",
                 "has-property-descriptors": "^1.0.2",
-                "has-proto": "^1.0.1",
+                "has-proto": "^1.0.3",
                 "has-symbols": "^1.0.3",
                 "internal-slot": "^1.0.7",
                 "iterator.prototype": "^1.1.2",
-                "safe-array-concat": "^1.1.0"
+                "safe-array-concat": "^1.1.2"
             },
             "engines": {
                 "node": ">= 0.4"
             }
         },
         "node_modules/es-module-lexer": {
-            "version": "1.4.1",
-            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
-            "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
+            "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw=="
+        },
+        "node_modules/es-object-atoms": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
+            "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
+            "dev": true,
+            "dependencies": {
+                "es-errors": "^1.3.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
         },
         "node_modules/es-set-tostringtag": {
             "version": "2.0.3",
@@ -16451,16 +16645,16 @@
             }
         },
         "node_modules/eslint": {
-            "version": "8.56.0",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
-            "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+            "version": "8.57.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+            "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
             "dev": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.2.0",
                 "@eslint-community/regexpp": "^4.6.1",
                 "@eslint/eslintrc": "^2.1.4",
-                "@eslint/js": "8.56.0",
-                "@humanwhocodes/config-array": "^0.11.13",
+                "@eslint/js": "8.57.0",
+                "@humanwhocodes/config-array": "^0.11.14",
                 "@humanwhocodes/module-importer": "^1.0.1",
                 "@nodelib/fs.walk": "^1.2.8",
                 "@ungap/structured-clone": "^1.2.0",
@@ -17434,9 +17628,9 @@
             }
         },
         "node_modules/eslint-module-utils": {
-            "version": "2.8.0",
-            "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
-            "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
+            "version": "2.8.1",
+            "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
+            "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
             "dev": true,
             "dependencies": {
                 "debug": "^3.2.7"
@@ -17692,27 +17886,29 @@
             }
         },
         "node_modules/eslint-plugin-react": {
-            "version": "7.33.2",
-            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
-            "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==",
+            "version": "7.34.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
+            "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
             "dev": true,
             "dependencies": {
-                "array-includes": "^3.1.6",
-                "array.prototype.flatmap": "^1.3.1",
-                "array.prototype.tosorted": "^1.1.1",
+                "array-includes": "^3.1.7",
+                "array.prototype.findlast": "^1.2.4",
+                "array.prototype.flatmap": "^1.3.2",
+                "array.prototype.toreversed": "^1.1.2",
+                "array.prototype.tosorted": "^1.1.3",
                 "doctrine": "^2.1.0",
-                "es-iterator-helpers": "^1.0.12",
+                "es-iterator-helpers": "^1.0.17",
                 "estraverse": "^5.3.0",
                 "jsx-ast-utils": "^2.4.1 || ^3.0.0",
                 "minimatch": "^3.1.2",
-                "object.entries": "^1.1.6",
-                "object.fromentries": "^2.0.6",
-                "object.hasown": "^1.1.2",
-                "object.values": "^1.1.6",
+                "object.entries": "^1.1.7",
+                "object.fromentries": "^2.0.7",
+                "object.hasown": "^1.1.3",
+                "object.values": "^1.1.7",
                 "prop-types": "^15.8.1",
-                "resolve": "^2.0.0-next.4",
+                "resolve": "^2.0.0-next.5",
                 "semver": "^6.3.1",
-                "string.prototype.matchall": "^4.0.8"
+                "string.prototype.matchall": "^4.0.10"
             },
             "engines": {
                 "node": ">=4"
@@ -18159,16 +18355,16 @@
             }
         },
         "node_modules/express": {
-            "version": "4.18.2",
-            "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
-            "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+            "version": "4.19.2",
+            "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+            "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
             "dependencies": {
                 "accepts": "~1.3.8",
                 "array-flatten": "1.1.1",
-                "body-parser": "1.20.1",
+                "body-parser": "1.20.2",
                 "content-disposition": "0.5.4",
                 "content-type": "~1.0.4",
-                "cookie": "0.5.0",
+                "cookie": "0.6.0",
                 "cookie-signature": "1.0.6",
                 "debug": "2.6.9",
                 "depd": "2.0.0",
@@ -18266,33 +18462,10 @@
                 "node": ">= 8.0.0"
             }
         },
-        "node_modules/express/node_modules/body-parser": {
-            "version": "1.20.1",
-            "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
-            "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
-            "dependencies": {
-                "bytes": "3.1.2",
-                "content-type": "~1.0.4",
-                "debug": "2.6.9",
-                "depd": "2.0.0",
-                "destroy": "1.2.0",
-                "http-errors": "2.0.0",
-                "iconv-lite": "0.4.24",
-                "on-finished": "2.4.1",
-                "qs": "6.11.0",
-                "raw-body": "2.5.1",
-                "type-is": "~1.6.18",
-                "unpipe": "1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8",
-                "npm": "1.2.8000 || >= 1.4.16"
-            }
-        },
         "node_modules/express/node_modules/cookie": {
-            "version": "0.5.0",
-            "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
-            "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+            "version": "0.6.0",
+            "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+            "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
             "engines": {
                 "node": ">= 0.6"
             }
@@ -18310,20 +18483,6 @@
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
             "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
-        "node_modules/express/node_modules/raw-body": {
-            "version": "2.5.1",
-            "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
-            "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
-            "dependencies": {
-                "bytes": "3.1.2",
-                "http-errors": "2.0.0",
-                "iconv-lite": "0.4.24",
-                "unpipe": "1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
         "node_modules/extend": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -18776,9 +18935,9 @@
             }
         },
         "node_modules/follow-redirects": {
-            "version": "1.15.5",
-            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
-            "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+            "version": "1.15.6",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+            "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
             "funding": [
                 {
                     "type": "individual",
@@ -19276,9 +19435,9 @@
             }
         },
         "node_modules/gaxios/node_modules/agent-base": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
-            "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+            "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
             "dependencies": {
                 "debug": "^4.3.4"
             },
@@ -19612,9 +19771,9 @@
             "integrity": "sha512-sIVQCiRWOymHbVD1Aw/T9/ijbPYAVGBlgGYd1N9MRKfcyBNSpjr87Vg9nSHm+RCT8ELrvK8IJYJV0QRJuVUkCQ=="
         },
         "node_modules/google-auth-library": {
-            "version": "9.6.3",
-            "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz",
-            "integrity": "sha512-4CacM29MLC2eT9Cey5GDVK4Q8t+MMp8+OEdOaqD9MG6b0dOyLORaaeJMPQ7EESVgm/+z5EKYyFLxgzBJlJgyHQ==",
+            "version": "9.7.0",
+            "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.7.0.tgz",
+            "integrity": "sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==",
             "dependencies": {
                 "base64-js": "^1.3.0",
                 "ecdsa-sig-formatter": "^1.0.11",
@@ -19652,13 +19811,13 @@
             }
         },
         "node_modules/googleapis-common": {
-            "version": "7.0.1",
-            "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.0.1.tgz",
-            "integrity": "sha512-mgt5zsd7zj5t5QXvDanjWguMdHAcJmmDrF9RkInCecNsyV7S7YtGqm5v2IWONNID88osb7zmx5FtrAP12JfD0w==",
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.1.0.tgz",
+            "integrity": "sha512-p3KHiWDBBWJEXk6SYauBEvxw5+UmRy7k2scxGtsNv9eHsTbpopJ3/7If4OrNnzJ9XMLg3IlyQXpVp8YPQsStiw==",
             "dependencies": {
                 "extend": "^3.0.2",
                 "gaxios": "^6.0.3",
-                "google-auth-library": "^9.0.0",
+                "google-auth-library": "^9.7.0",
                 "qs": "^6.7.0",
                 "url-template": "^2.0.8",
                 "uuid": "^9.0.0"
@@ -19692,9 +19851,9 @@
             }
         },
         "node_modules/got": {
-            "version": "14.2.0",
-            "resolved": "https://registry.npmjs.org/got/-/got-14.2.0.tgz",
-            "integrity": "sha512-dBq2KkHcQl3AwPoIWsLsQScCPpUgRulz1qZVthjPYKYOPmYfBnekR3vxecjZbm91Vc3JUGnV9mqFX7B+Fe2quw==",
+            "version": "14.2.1",
+            "resolved": "https://registry.npmjs.org/got/-/got-14.2.1.tgz",
+            "integrity": "sha512-KOaPMremmsvx6l9BLC04LYE6ZFW4x7e4HkTe3LwBmtuYYQwpeS4XKqzhubTIkaQ1Nr+eXxeori0zuwupXMovBQ==",
             "dependencies": {
                 "@sindresorhus/is": "^6.1.0",
                 "@szmarczak/http-timer": "^5.0.1",
@@ -19840,9 +19999,9 @@
             "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
         },
         "node_modules/hasown": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
-            "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+            "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
             "dependencies": {
                 "function-bind": "^1.1.2"
             },
@@ -20083,9 +20242,9 @@
             }
         },
         "node_modules/html-entities": {
-            "version": "2.4.0",
-            "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz",
-            "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==",
+            "version": "2.5.2",
+            "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
+            "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
             "dev": true,
             "funding": [
                 {
@@ -20282,9 +20441,9 @@
             }
         },
         "node_modules/http-proxy-agent/node_modules/agent-base": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
-            "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+            "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
             "dev": true,
             "dependencies": {
                 "debug": "^4.3.4"
@@ -20778,9 +20937,9 @@
             "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
         },
         "node_modules/inline-style-parser": {
-            "version": "0.2.2",
-            "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz",
-            "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ=="
+            "version": "0.2.3",
+            "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
+            "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g=="
         },
         "node_modules/inquirer": {
             "version": "7.3.3",
@@ -21216,6 +21375,21 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-data-view": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
+            "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
+            "dev": true,
+            "dependencies": {
+                "is-typed-array": "^1.1.13"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-date-object": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -21350,10 +21524,13 @@
             }
         },
         "node_modules/is-map": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
-            "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+            "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
             "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
@@ -21451,10 +21628,13 @@
             }
         },
         "node_modules/is-set": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
-            "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+            "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
             "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
@@ -21547,10 +21727,13 @@
             }
         },
         "node_modules/is-weakmap": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
-            "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+            "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
             "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
@@ -21568,13 +21751,16 @@
             }
         },
         "node_modules/is-weakset": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
-            "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+            "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "get-intrinsic": "^1.1.1"
+                "call-bind": "^1.0.7",
+                "get-intrinsic": "^1.2.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -21708,13 +21894,13 @@
             }
         },
         "node_modules/jimp": {
-            "version": "0.22.10",
-            "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.10.tgz",
-            "integrity": "sha512-lCaHIJAgTOsplyJzC1w/laxSxrbSsEBw4byKwXgUdMmh+ayPsnidTblenQm+IvhIs44Gcuvlb6pd2LQ0wcKaKg==",
+            "version": "0.22.12",
+            "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz",
+            "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==",
             "dependencies": {
-                "@jimp/custom": "^0.22.10",
-                "@jimp/plugins": "^0.22.10",
-                "@jimp/types": "^0.22.10",
+                "@jimp/custom": "^0.22.12",
+                "@jimp/plugins": "^0.22.12",
+                "@jimp/types": "^0.22.12",
                 "regenerator-runtime": "^0.13.3"
             }
         },
@@ -21862,9 +22048,9 @@
             }
         },
         "node_modules/jsdom/node_modules/agent-base": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
-            "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+            "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
             "dev": true,
             "dependencies": {
                 "debug": "^4.3.4"
@@ -22361,6 +22547,11 @@
             "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
             "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w=="
         },
+        "node_modules/lodash.clonedeep": {
+            "version": "4.5.0",
+            "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+            "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
+        },
         "node_modules/lodash.debounce": {
             "version": "4.0.8",
             "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -22524,9 +22715,9 @@
             }
         },
         "node_modules/magic-string": {
-            "version": "0.30.7",
-            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
-            "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
+            "version": "0.30.8",
+            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
+            "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
             "dependencies": {
                 "@jridgewell/sourcemap-codec": "^1.4.15"
             },
@@ -22627,13 +22818,13 @@
             "dev": true
         },
         "node_modules/mapbox-gl": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.1.2.tgz",
-            "integrity": "sha512-+KoLEqZM8GxO/ViPz9tKgGURteKne+Y0pXiVCNsowymiZFH3FiL6dt9oZE95owMg5XqD3Kygz5mfchR1ZgmWlA==",
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.2.0.tgz",
+            "integrity": "sha512-v8S7x+wTr35kJ9nqzgn/VPiSFZxBkyQhwCk9bdyiFHVwCukNGG3LXt03FoaHHTsOuB9JWenWE96k0Uw+HGMZ8w==",
             "dependencies": {
                 "@mapbox/geojson-rewind": "^0.5.2",
                 "@mapbox/jsonlint-lines-primitives": "^2.0.2",
-                "@mapbox/mapbox-gl-supported": "^2.0.1",
+                "@mapbox/mapbox-gl-supported": "^3.0.0",
                 "@mapbox/point-geometry": "^0.1.0",
                 "@mapbox/tiny-sdf": "^2.0.6",
                 "@mapbox/unitbezier": "^0.0.1",
@@ -22646,13 +22837,16 @@
                 "gl-matrix": "^3.4.3",
                 "grid-index": "^1.1.0",
                 "kdbush": "^4.0.1",
+                "lodash.clonedeep": "^4.5.0",
                 "murmurhash-js": "^1.0.0",
                 "pbf": "^3.2.1",
                 "potpack": "^2.0.0",
                 "quickselect": "^2.0.0",
                 "rw": "^1.3.3",
+                "serialize-to-js": "^3.1.2",
                 "supercluster": "^8.0.0",
                 "tinyqueue": "^2.0.3",
+                "tweakpane": "^4.0.3",
                 "vt-pbf": "^3.1.3"
             }
         },
@@ -22875,9 +23069,9 @@
             }
         },
         "node_modules/mdast-util-mdx-jsx": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz",
-            "integrity": "sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==",
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz",
+            "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==",
             "dependencies": {
                 "@types/estree-jsx": "^1.0.0",
                 "@types/hast": "^3.0.0",
@@ -23719,20 +23913,20 @@
             "integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g=="
         },
         "node_modules/mobx": {
-            "version": "6.12.0",
-            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.0.tgz",
-            "integrity": "sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ==",
+            "version": "6.12.3",
+            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.3.tgz",
+            "integrity": "sha512-c8NKkO4R2lShkSXZ2Ongj1ycjugjzFFo/UswHBnS62y07DMcTc9Rvo03/3nRyszIvwPNljlkd4S828zIBv/piw==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mobx"
             }
         },
         "node_modules/mobx-react": {
-            "version": "9.1.0",
-            "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.0.tgz",
-            "integrity": "sha512-DeDRTYw4AlgHw8xEXtiZdKKEnp+c5/jeUgTbTQXEqnAzfkrgYRWP3p3Nv3Whc2CEcM/mDycbDWGjxKokQdlffg==",
+            "version": "9.1.1",
+            "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.1.tgz",
+            "integrity": "sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw==",
             "dependencies": {
-                "mobx-react-lite": "^4.0.4"
+                "mobx-react-lite": "^4.0.7"
             },
             "funding": {
                 "type": "opencollective",
@@ -23752,9 +23946,9 @@
             }
         },
         "node_modules/mobx-react-lite": {
-            "version": "4.0.5",
-            "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.5.tgz",
-            "integrity": "sha512-StfB2wxE8imKj1f6T8WWPf4lVMx3cYH9Iy60bbKXEs21+HQ4tvvfIBZfSmMXgQAefi8xYEwQIz4GN9s0d2h7dg==",
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.7.tgz",
+            "integrity": "sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==",
             "dependencies": {
                 "use-sync-external-store": "^1.2.0"
             },
@@ -23784,9 +23978,9 @@
             }
         },
         "node_modules/mocha": {
-            "version": "10.3.0",
-            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz",
-            "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==",
+            "version": "10.4.0",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz",
+            "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==",
             "dev": true,
             "dependencies": {
                 "ansi-colors": "4.1.1",
@@ -23927,12 +24121,12 @@
             }
         },
         "node_modules/mongodb": {
-            "version": "6.3.0",
-            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz",
-            "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==",
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz",
+            "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==",
             "dependencies": {
-                "@mongodb-js/saslprep": "^1.1.0",
-                "bson": "^6.2.0",
+                "@mongodb-js/saslprep": "^1.1.5",
+                "bson": "^6.4.0",
                 "mongodb-connection-string-url": "^3.0.0"
             },
             "engines": {
@@ -24004,9 +24198,9 @@
             }
         },
         "node_modules/mongoose": {
-            "version": "8.2.0",
-            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.0.tgz",
-            "integrity": "sha512-la93n6zCYRbPS+c5N9oTDAktvREy5OT9OCljp1Tah0y3+p8UPMTAoabWaLZMdzYruOtF9/9GRf6MasaZjiZP1A==",
+            "version": "8.2.4",
+            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.4.tgz",
+            "integrity": "sha512-da/r6zpG+2eAXuhBGUnL6jcBd03zlytoCc5/wq+LyTsmrY9hhPQmSpnugwnfqldtBmUOhB6iMLoV4hNtHRq+ww==",
             "dependencies": {
                 "bson": "^6.2.0",
                 "kareem": "2.5.1",
@@ -24024,6 +24218,51 @@
                 "url": "https://opencollective.com/mongoose"
             }
         },
+        "node_modules/mongoose/node_modules/mongodb": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz",
+            "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==",
+            "dependencies": {
+                "@mongodb-js/saslprep": "^1.1.0",
+                "bson": "^6.2.0",
+                "mongodb-connection-string-url": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=16.20.1"
+            },
+            "peerDependencies": {
+                "@aws-sdk/credential-providers": "^3.188.0",
+                "@mongodb-js/zstd": "^1.1.0",
+                "gcp-metadata": "^5.2.0",
+                "kerberos": "^2.0.1",
+                "mongodb-client-encryption": ">=6.0.0 <7",
+                "snappy": "^7.2.2",
+                "socks": "^2.7.1"
+            },
+            "peerDependenciesMeta": {
+                "@aws-sdk/credential-providers": {
+                    "optional": true
+                },
+                "@mongodb-js/zstd": {
+                    "optional": true
+                },
+                "gcp-metadata": {
+                    "optional": true
+                },
+                "kerberos": {
+                    "optional": true
+                },
+                "mongodb-client-encryption": {
+                    "optional": true
+                },
+                "snappy": {
+                    "optional": true
+                },
+                "socks": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/mongoose/node_modules/ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -24126,9 +24365,9 @@
             "dev": true
         },
         "node_modules/nan": {
-            "version": "2.18.0",
-            "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
-            "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w=="
+            "version": "2.19.0",
+            "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
+            "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw=="
         },
         "node_modules/nanoid": {
             "version": "3.3.7",
@@ -24306,9 +24545,9 @@
             }
         },
         "node_modules/nodemailer": {
-            "version": "6.9.10",
-            "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.10.tgz",
-            "integrity": "sha512-qtoKfGFhvIFW5kLfrkw2R6Nm6Ur4LNUMykyqu6n9BRKJuyQrqEGwdXXUAbwWEKt33dlWUGXb7rzmJP/p4+O+CA==",
+            "version": "6.9.13",
+            "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz",
+            "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==",
             "engines": {
                 "node": ">=6.0.0"
             }
@@ -24413,9 +24652,9 @@
             }
         },
         "node_modules/normalize-url": {
-            "version": "8.0.0",
-            "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
-            "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==",
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz",
+            "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==",
             "engines": {
                 "node": ">=14.16"
             },
@@ -24429,9 +24668,9 @@
             "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
         },
         "node_modules/npm": {
-            "version": "10.4.0",
-            "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz",
-            "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==",
+            "version": "10.5.0",
+            "resolved": "https://registry.npmjs.org/npm/-/npm-10.5.0.tgz",
+            "integrity": "sha512-Ejxwvfh9YnWVU2yA5FzoYLTW52vxHCz+MHrOFg9Cc8IFgF/6f5AGPAvb5WTay5DIUP1NIfN3VBZ0cLlGO0Ys+A==",
             "bundleDependencies": [
                 "@isaacs/string-locale-compare",
                 "@npmcli/arborist",
@@ -24513,7 +24752,7 @@
                 "@npmcli/package-json": "^5.0.0",
                 "@npmcli/promise-spawn": "^7.0.1",
                 "@npmcli/run-script": "^7.0.4",
-                "@sigstore/tuf": "^2.3.0",
+                "@sigstore/tuf": "^2.3.1",
                 "abbrev": "^2.0.0",
                 "archy": "~1.0.0",
                 "cacache": "^18.0.2",
@@ -24564,7 +24803,7 @@
                 "proc-log": "^3.0.0",
                 "qrcode-terminal": "^0.12.0",
                 "read": "^2.1.0",
-                "semver": "^7.5.4",
+                "semver": "^7.6.0",
                 "spdx-expression-parse": "^3.0.1",
                 "ssri": "^10.0.5",
                 "supports-color": "^9.4.0",
@@ -24673,7 +24912,7 @@
             "license": "ISC"
         },
         "node_modules/npm/node_modules/@npmcli/agent": {
-            "version": "2.2.0",
+            "version": "2.2.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -24688,7 +24927,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/arborist": {
-            "version": "7.3.1",
+            "version": "7.4.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -24700,7 +24939,7 @@
                 "@npmcli/name-from-folder": "^2.0.0",
                 "@npmcli/node-gyp": "^3.0.0",
                 "@npmcli/package-json": "^5.0.0",
-                "@npmcli/query": "^3.0.1",
+                "@npmcli/query": "^3.1.0",
                 "@npmcli/run-script": "^7.0.2",
                 "bin-links": "^4.0.1",
                 "cacache": "^18.0.0",
@@ -24734,7 +24973,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/config": {
-            "version": "8.1.0",
+            "version": "8.2.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -24893,7 +25132,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/query": {
-            "version": "3.0.1",
+            "version": "3.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -24928,18 +25167,18 @@
             }
         },
         "node_modules/npm/node_modules/@sigstore/bundle": {
-            "version": "2.1.1",
+            "version": "2.2.0",
             "inBundle": true,
             "license": "Apache-2.0",
             "dependencies": {
-                "@sigstore/protobuf-specs": "^0.2.1"
+                "@sigstore/protobuf-specs": "^0.3.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/@sigstore/core": {
-            "version": "0.2.0",
+            "version": "1.0.0",
             "inBundle": true,
             "license": "Apache-2.0",
             "engines": {
@@ -24947,7 +25186,7 @@
             }
         },
         "node_modules/npm/node_modules/@sigstore/protobuf-specs": {
-            "version": "0.2.1",
+            "version": "0.3.0",
             "inBundle": true,
             "license": "Apache-2.0",
             "engines": {
@@ -24955,13 +25194,13 @@
             }
         },
         "node_modules/npm/node_modules/@sigstore/sign": {
-            "version": "2.2.1",
+            "version": "2.2.3",
             "inBundle": true,
             "license": "Apache-2.0",
             "dependencies": {
-                "@sigstore/bundle": "^2.1.1",
-                "@sigstore/core": "^0.2.0",
-                "@sigstore/protobuf-specs": "^0.2.1",
+                "@sigstore/bundle": "^2.2.0",
+                "@sigstore/core": "^1.0.0",
+                "@sigstore/protobuf-specs": "^0.3.0",
                 "make-fetch-happen": "^13.0.0"
             },
             "engines": {
@@ -24969,11 +25208,11 @@
             }
         },
         "node_modules/npm/node_modules/@sigstore/tuf": {
-            "version": "2.3.0",
+            "version": "2.3.1",
             "inBundle": true,
             "license": "Apache-2.0",
             "dependencies": {
-                "@sigstore/protobuf-specs": "^0.2.1",
+                "@sigstore/protobuf-specs": "^0.3.0",
                 "tuf-js": "^2.2.0"
             },
             "engines": {
@@ -24981,13 +25220,13 @@
             }
         },
         "node_modules/npm/node_modules/@sigstore/verify": {
-            "version": "0.1.0",
+            "version": "1.1.0",
             "inBundle": true,
             "license": "Apache-2.0",
             "dependencies": {
-                "@sigstore/bundle": "^2.1.1",
-                "@sigstore/core": "^0.2.0",
-                "@sigstore/protobuf-specs": "^0.2.1"
+                "@sigstore/bundle": "^2.2.0",
+                "@sigstore/core": "^1.0.0",
+                "@sigstore/protobuf-specs": "^0.3.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
@@ -25357,7 +25596,7 @@
             }
         },
         "node_modules/npm/node_modules/diff": {
-            "version": "5.1.0",
+            "version": "5.2.0",
             "inBundle": true,
             "license": "BSD-3-Clause",
             "engines": {
@@ -25493,7 +25732,7 @@
             "license": "ISC"
         },
         "node_modules/npm/node_modules/hasown": {
-            "version": "2.0.0",
+            "version": "2.0.1",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
@@ -25520,7 +25759,7 @@
             "license": "BSD-2-Clause"
         },
         "node_modules/npm/node_modules/http-proxy-agent": {
-            "version": "7.0.0",
+            "version": "7.0.2",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
@@ -25532,7 +25771,7 @@
             }
         },
         "node_modules/npm/node_modules/https-proxy-agent": {
-            "version": "7.0.2",
+            "version": "7.0.4",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
@@ -25607,10 +25846,22 @@
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
-        "node_modules/npm/node_modules/ip": {
-            "version": "2.0.0",
+        "node_modules/npm/node_modules/ip-address": {
+            "version": "9.0.5",
             "inBundle": true,
-            "license": "MIT"
+            "license": "MIT",
+            "dependencies": {
+                "jsbn": "1.1.0",
+                "sprintf-js": "^1.1.3"
+            },
+            "engines": {
+                "node": ">= 12"
+            }
+        },
+        "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": {
+            "version": "1.1.3",
+            "inBundle": true,
+            "license": "BSD-3-Clause"
         },
         "node_modules/npm/node_modules/ip-regex": {
             "version": "5.0.0",
@@ -25680,6 +25931,11 @@
                 "@pkgjs/parseargs": "^0.11.0"
             }
         },
+        "node_modules/npm/node_modules/jsbn": {
+            "version": "1.1.0",
+            "inBundle": true,
+            "license": "MIT"
+        },
         "node_modules/npm/node_modules/json-parse-even-better-errors": {
             "version": "3.0.1",
             "inBundle": true,
@@ -25727,7 +25983,7 @@
             }
         },
         "node_modules/npm/node_modules/libnpmdiff": {
-            "version": "6.0.6",
+            "version": "6.0.7",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25746,7 +26002,7 @@
             }
         },
         "node_modules/npm/node_modules/libnpmexec": {
-            "version": "7.0.7",
+            "version": "7.0.8",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25767,7 +26023,7 @@
             }
         },
         "node_modules/npm/node_modules/libnpmfund": {
-            "version": "5.0.4",
+            "version": "5.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25802,7 +26058,7 @@
             }
         },
         "node_modules/npm/node_modules/libnpmpack": {
-            "version": "6.0.6",
+            "version": "6.0.7",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25872,7 +26128,7 @@
             }
         },
         "node_modules/npm/node_modules/lru-cache": {
-            "version": "10.1.0",
+            "version": "10.2.0",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -26481,7 +26737,7 @@
             "optional": true
         },
         "node_modules/npm/node_modules/semver": {
-            "version": "7.5.4",
+            "version": "7.6.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -26541,16 +26797,16 @@
             }
         },
         "node_modules/npm/node_modules/sigstore": {
-            "version": "2.2.0",
+            "version": "2.2.2",
             "inBundle": true,
             "license": "Apache-2.0",
             "dependencies": {
-                "@sigstore/bundle": "^2.1.1",
-                "@sigstore/core": "^0.2.0",
-                "@sigstore/protobuf-specs": "^0.2.1",
-                "@sigstore/sign": "^2.2.1",
-                "@sigstore/tuf": "^2.3.0",
-                "@sigstore/verify": "^0.1.0"
+                "@sigstore/bundle": "^2.2.0",
+                "@sigstore/core": "^1.0.0",
+                "@sigstore/protobuf-specs": "^0.3.0",
+                "@sigstore/sign": "^2.2.3",
+                "@sigstore/tuf": "^2.3.1",
+                "@sigstore/verify": "^1.1.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
@@ -26566,15 +26822,15 @@
             }
         },
         "node_modules/npm/node_modules/socks": {
-            "version": "2.7.1",
+            "version": "2.8.0",
             "inBundle": true,
             "license": "MIT",
             "dependencies": {
-                "ip": "^2.0.0",
+                "ip-address": "^9.0.5",
                 "smart-buffer": "^4.2.0"
             },
             "engines": {
-                "node": ">= 10.13.0",
+                "node": ">= 16.0.0",
                 "npm": ">= 3.0.0"
             }
         },
@@ -26601,7 +26857,7 @@
             }
         },
         "node_modules/npm/node_modules/spdx-exceptions": {
-            "version": "2.3.0",
+            "version": "2.5.0",
             "inBundle": true,
             "license": "CC-BY-3.0"
         },
@@ -26615,7 +26871,7 @@
             }
         },
         "node_modules/npm/node_modules/spdx-license-ids": {
-            "version": "3.0.16",
+            "version": "3.0.17",
             "inBundle": true,
             "license": "CC0-1.0"
         },
@@ -27026,12 +27282,12 @@
             }
         },
         "node_modules/object-is": {
-            "version": "1.1.5",
-            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
-            "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+            "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.1.3"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -27126,28 +27382,29 @@
             }
         },
         "node_modules/object.entries": {
-            "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz",
-            "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==",
+            "version": "1.1.8",
+            "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
+            "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-object-atoms": "^1.0.0"
             },
             "engines": {
                 "node": ">= 0.4"
             }
         },
         "node_modules/object.fromentries": {
-            "version": "2.0.7",
-            "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
-            "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+            "version": "2.0.8",
+            "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+            "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.2",
+                "es-object-atoms": "^1.0.0"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -27157,40 +27414,45 @@
             }
         },
         "node_modules/object.groupby": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz",
-            "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==",
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+            "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
             "dev": true,
             "dependencies": {
-                "array.prototype.filter": "^1.0.3",
-                "call-bind": "^1.0.5",
+                "call-bind": "^1.0.7",
                 "define-properties": "^1.2.1",
-                "es-abstract": "^1.22.3",
-                "es-errors": "^1.0.0"
+                "es-abstract": "^1.23.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
             }
         },
         "node_modules/object.hasown": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz",
-            "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==",
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz",
+            "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==",
             "dev": true,
             "dependencies": {
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.2",
+                "es-object-atoms": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
         "node_modules/object.values": {
-            "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
-            "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
+            "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-object-atoms": "^1.0.0"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -27270,9 +27532,9 @@
             }
         },
         "node_modules/openai": {
-            "version": "4.28.0",
-            "resolved": "https://registry.npmjs.org/openai/-/openai-4.28.0.tgz",
-            "integrity": "sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q==",
+            "version": "4.31.0",
+            "resolved": "https://registry.npmjs.org/openai/-/openai-4.31.0.tgz",
+            "integrity": "sha512-JebkRnRGEGLnJt3+bJ5B7au8nBeZvJjs9baVxDmUZ5+BgafAdy6KDxJGSuyaw/IA+ErqY3jmOH5cDC2mCDJF2w==",
             "dependencies": {
                 "@types/node": "^18.11.18",
                 "@types/node-fetch": "^2.6.4",
@@ -27289,9 +27551,9 @@
             }
         },
         "node_modules/openai/node_modules/@types/node": {
-            "version": "18.19.18",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.18.tgz",
-            "integrity": "sha512-80CP7B8y4PzZF0GWx15/gVWRrB5y/bIjNI84NK3cmQJu0WZwvmj2WMA5LcofQFVfLqqCSp545+U2LsrVzX36Zg==",
+            "version": "18.19.28",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.28.tgz",
+            "integrity": "sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -27671,11 +27933,11 @@
             "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
         },
         "node_modules/path-scurry": {
-            "version": "1.10.1",
-            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
-            "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+            "version": "1.10.2",
+            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+            "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
             "dependencies": {
-                "lru-cache": "^9.1.1 || ^10.0.0",
+                "lru-cache": "^10.2.0",
                 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
             },
             "engines": {
@@ -27706,13 +27968,25 @@
                 "node": ">=8"
             }
         },
+        "node_modules/path2d": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.1.1.tgz",
+            "integrity": "sha512-/+S03c8AGsDYKKBtRDqieTJv2GlkMb0bWjnqOgtF6MkjdUQ9a8ARAtxWf9NgKLGm2+WQr6+/tqJdU8HNGsIDoA==",
+            "optional": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/path2d-polyfill": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
-            "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.1.1.tgz",
+            "integrity": "sha512-4Rka5lN+rY/p0CdD8+E+BFv51lFaFvJOrlOhyQ+zjzyQrzyh3ozmxd1vVGGDdIbUFSBtIZLSnspxTgPT0iJhvA==",
             "optional": true,
+            "dependencies": {
+                "path2d": "0.1.1"
+            },
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
         "node_modules/pathval": {
@@ -28001,9 +28275,9 @@
             }
         },
         "node_modules/postcss": {
-            "version": "8.4.35",
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
-            "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+            "version": "8.4.38",
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+            "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -28021,7 +28295,7 @@
             "dependencies": {
                 "nanoid": "^3.3.7",
                 "picocolors": "^1.0.0",
-                "source-map-js": "^1.0.2"
+                "source-map-js": "^1.2.0"
             },
             "engines": {
                 "node": "^10 || ^12 || >=14"
@@ -28083,9 +28357,9 @@
             }
         },
         "node_modules/postcss-selector-parser": {
-            "version": "6.0.15",
-            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
-            "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+            "version": "6.0.16",
+            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
+            "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
             "dependencies": {
                 "cssesc": "^3.0.0",
                 "util-deprecate": "^1.0.2"
@@ -28248,9 +28522,9 @@
             }
         },
         "node_modules/prosemirror-history": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.2.tgz",
-            "integrity": "sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==",
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.0.tgz",
+            "integrity": "sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==",
             "dependencies": {
                 "prosemirror-state": "^1.2.2",
                 "prosemirror-transform": "^1.0.0",
@@ -28313,9 +28587,9 @@
             }
         },
         "node_modules/prosemirror-view": {
-            "version": "1.33.1",
-            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.1.tgz",
-            "integrity": "sha512-62qkYgSJIkwIMMCpuGuPzc52DiK1Iod6TWoIMxP4ja6BTD4yO8kCUL64PZ/WhH/dJ9fW0CDO39FhH1EMyhUFEg==",
+            "version": "1.33.3",
+            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.3.tgz",
+            "integrity": "sha512-P4Ao/bc4OrU/2yLIf8dL4lJaEtjLR3QjIvQHgJYp2jUS7kYM4bSR6okbBjkqzOs/FwUon6UGjTLdKMnPL1MZqw==",
             "dependencies": {
                 "prosemirror-model": "^1.16.0",
                 "prosemirror-state": "^1.0.0",
@@ -28678,9 +28952,9 @@
             }
         },
         "node_modules/rc-util": {
-            "version": "5.38.2",
-            "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.38.2.tgz",
-            "integrity": "sha512-yRGRPKyi84H7NkRSP6FzEIYBdUt4ufdsmXUZ7qM2H5qoByPax70NnGPkfo36N+UKUnUBj2f2Q2eUbwYMuAsIOQ==",
+            "version": "5.39.1",
+            "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.39.1.tgz",
+            "integrity": "sha512-OW/ERynNDgNr4y0oiFmtes3rbEamXw7GHGbkbNd9iRr7kgT03T6fT0b9WpJ3mbxKhyOcAHnGcIoh5u/cjrC2OQ==",
             "dependencies": {
                 "@babel/runtime": "^7.18.3",
                 "react-is": "^18.2.0"
@@ -28783,12 +29057,12 @@
             "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
         },
         "node_modules/react-datepicker": {
-            "version": "6.1.0",
-            "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.1.0.tgz",
-            "integrity": "sha512-8uz+hAOpvHqZGvD4Ky1hJ0/tLI4S9B0Gu9LV7LtLxRKXODs/xrxEay0aMVp7AW9iizTeImZh/6aA00fFaRZpJw==",
+            "version": "6.6.0",
+            "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.6.0.tgz",
+            "integrity": "sha512-ERC0/Q4pPC9bNIcGUpdCbHc+oCxhkU3WI3UOGHkyJ3A9fqALCYpEmLc5S5xvAd7DuCDdbsyW97oRPM6pWWwjww==",
             "dependencies": {
                 "@floating-ui/react": "^0.26.2",
-                "classnames": "^2.2.6",
+                "clsx": "^2.1.0",
                 "date-fns": "^3.3.1",
                 "prop-types": "^15.7.2",
                 "react-onclickoutside": "^6.13.0"
@@ -28799,9 +29073,9 @@
             }
         },
         "node_modules/react-datepicker/node_modules/date-fns": {
-            "version": "3.3.1",
-            "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
-            "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+            "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
             "funding": {
                 "type": "github",
                 "url": "https://github.com/sponsors/kossnocorp"
@@ -28912,13 +29186,13 @@
             }
         },
         "node_modules/react-jsx-parser/node_modules/@types/react": {
-            "version": "17.0.75",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.75.tgz",
-            "integrity": "sha512-MSA+NzEzXnQKrqpO63CYqNstFjsESgvJAdAyyJ1n6ZQq/GLgf6nOfIKwk+Twuz0L1N6xPe+qz5xRCJrbhMaLsw==",
+            "version": "17.0.80",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz",
+            "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==",
             "optional": true,
             "dependencies": {
                 "@types/prop-types": "*",
-                "@types/scheduler": "*",
+                "@types/scheduler": "^0.16",
                 "csstype": "^3.0.2"
             }
         },
@@ -29064,9 +29338,9 @@
             }
         },
         "node_modules/react-smooth": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz",
-            "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==",
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
+            "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
             "dependencies": {
                 "fast-equals": "^5.0.1",
                 "prop-types": "^15.8.1",
@@ -29216,9 +29490,9 @@
             "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
         },
         "node_modules/recharts": {
-            "version": "2.12.1",
-            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.1.tgz",
-            "integrity": "sha512-35vUCEBPf+pM+iVgSgVTn86faKya5pc4JO6cYJL63qOK2zDEyzDn20Tdj+CDI/3z+VcpKyQ8ZBQ9OiQ+vuAbjg==",
+            "version": "2.12.3",
+            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.3.tgz",
+            "integrity": "sha512-vE/F7wTlokf5mtCqVDJlVKelCjliLSJ+DJxj79XlMREm7gpV7ljwbrwE3CfeaoDlOaLX+6iwHaVRn9587YkwIg==",
             "dependencies": {
                 "clsx": "^2.0.0",
                 "eventemitter3": "^4.0.1",
@@ -29270,16 +29544,16 @@
             }
         },
         "node_modules/reflect.getprototypeof": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz",
-            "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
+            "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.5",
+                "call-bind": "^1.0.7",
                 "define-properties": "^1.2.1",
-                "es-abstract": "^1.22.3",
-                "es-errors": "^1.0.0",
-                "get-intrinsic": "^1.2.3",
+                "es-abstract": "^1.23.1",
+                "es-errors": "^1.3.0",
+                "get-intrinsic": "^1.2.4",
                 "globalthis": "^1.0.3",
                 "which-builtin-type": "^1.1.3"
             },
@@ -29800,9 +30074,9 @@
             }
         },
         "node_modules/reveal.js": {
-            "version": "5.0.4",
-            "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-5.0.4.tgz",
-            "integrity": "sha512-480pVhre9SXWuE4QbDwG0nPrip3TkifflqaKQWF8Ynf4iYIUBfgu5leeMso0srubQsZQ+G2OzktAfAkrvBY0Ww==",
+            "version": "5.0.5",
+            "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-5.0.5.tgz",
+            "integrity": "sha512-MPWPV/cRlkZhh72dAGYv/bUCr9ulwM2/ucCqiL/KN4tvhb6VvN49iwOyWHE08wppj8lMQXi2xbS3kyKgfyTYqg==",
             "engines": {
                 "node": ">=18.0.0"
             }
@@ -29825,15 +30099,15 @@
             }
         },
         "node_modules/rimraf/node_modules/glob": {
-            "version": "10.3.10",
-            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
-            "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+            "version": "10.3.12",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+            "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
             "dependencies": {
                 "foreground-child": "^3.1.0",
-                "jackspeak": "^2.3.5",
+                "jackspeak": "^2.3.6",
                 "minimatch": "^9.0.1",
-                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
-                "path-scurry": "^1.10.1"
+                "minipass": "^7.0.4",
+                "path-scurry": "^1.10.2"
             },
             "bin": {
                 "glob": "dist/esm/bin.mjs"
@@ -29846,9 +30120,9 @@
             }
         },
         "node_modules/rimraf/node_modules/minimatch": {
-            "version": "9.0.3",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
-            "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+            "version": "9.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
             "dependencies": {
                 "brace-expansion": "^2.0.1"
             },
@@ -29859,6 +30133,14 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/rimraf/node_modules/minipass": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            }
+        },
         "node_modules/robust-predicates": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz",
@@ -29931,13 +30213,13 @@
             "dev": true
         },
         "node_modules/safe-array-concat": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
-            "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==",
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
+            "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.5",
-                "get-intrinsic": "^1.2.2",
+                "call-bind": "^1.0.7",
+                "get-intrinsic": "^1.2.4",
                 "has-symbols": "^1.0.3",
                 "isarray": "^2.0.5"
             },
@@ -29990,9 +30272,9 @@
             "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
         },
         "node_modules/sass": {
-            "version": "1.71.1",
-            "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
-            "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
+            "version": "1.72.0",
+            "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz",
+            "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
             "dependencies": {
                 "chokidar": ">=3.0.0 <4.0.0",
                 "immutable": "^4.0.0",
@@ -30224,6 +30506,14 @@
                 "randombytes": "^2.1.0"
             }
         },
+        "node_modules/serialize-to-js": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz",
+            "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==",
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
         "node_modules/serializr": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/serializr/-/serializr-3.0.2.tgz",
@@ -30327,16 +30617,16 @@
             "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
         },
         "node_modules/set-function-length": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
-            "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+            "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
             "dependencies": {
-                "define-data-property": "^1.1.2",
+                "define-data-property": "^1.1.4",
                 "es-errors": "^1.3.0",
                 "function-bind": "^1.1.2",
-                "get-intrinsic": "^1.2.3",
+                "get-intrinsic": "^1.2.4",
                 "gopd": "^1.0.1",
-                "has-property-descriptors": "^1.0.1"
+                "has-property-descriptors": "^1.0.2"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -30485,11 +30775,11 @@
             }
         },
         "node_modules/side-channel": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
-            "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+            "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
             "dependencies": {
-                "call-bind": "^1.0.6",
+                "call-bind": "^1.0.7",
                 "es-errors": "^1.3.0",
                 "get-intrinsic": "^1.2.4",
                 "object-inspect": "^1.13.1"
@@ -30645,9 +30935,9 @@
             }
         },
         "node_modules/socket.io": {
-            "version": "4.7.4",
-            "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz",
-            "integrity": "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==",
+            "version": "4.7.5",
+            "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
+            "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
             "dependencies": {
                 "accepts": "~1.3.4",
                 "base64id": "~2.0.0",
@@ -30691,9 +30981,9 @@
             }
         },
         "node_modules/socket.io-client": {
-            "version": "4.7.4",
-            "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
-            "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
+            "version": "4.7.5",
+            "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
+            "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
             "dependencies": {
                 "@socket.io/component-emitter": "~3.1.0",
                 "debug": "~4.3.2",
@@ -30777,9 +31067,9 @@
             }
         },
         "node_modules/source-map-js": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-            "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+            "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -31059,34 +31349,41 @@
             "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
         },
         "node_modules/string.prototype.matchall": {
-            "version": "4.0.10",
-            "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
-            "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==",
+            "version": "4.0.11",
+            "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
+            "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1",
-                "get-intrinsic": "^1.2.1",
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.2",
+                "es-errors": "^1.3.0",
+                "es-object-atoms": "^1.0.0",
+                "get-intrinsic": "^1.2.4",
+                "gopd": "^1.0.1",
                 "has-symbols": "^1.0.3",
-                "internal-slot": "^1.0.5",
-                "regexp.prototype.flags": "^1.5.0",
-                "set-function-name": "^2.0.0",
-                "side-channel": "^1.0.4"
+                "internal-slot": "^1.0.7",
+                "regexp.prototype.flags": "^1.5.2",
+                "set-function-name": "^2.0.2",
+                "side-channel": "^1.0.6"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
         "node_modules/string.prototype.trim": {
-            "version": "1.2.8",
-            "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
-            "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
+            "version": "1.2.9",
+            "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
+            "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-abstract": "^1.23.0",
+                "es-object-atoms": "^1.0.0"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -31096,28 +31393,31 @@
             }
         },
         "node_modules/string.prototype.trimend": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
-            "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz",
+            "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-object-atoms": "^1.0.0"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
         "node_modules/string.prototype.trimstart": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
-            "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+            "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.2.0",
-                "es-abstract": "^1.22.1"
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1",
+                "es-object-atoms": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -31264,11 +31564,11 @@
             }
         },
         "node_modules/style-to-object": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz",
-            "integrity": "sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz",
+            "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==",
             "dependencies": {
-                "inline-style-parser": "0.2.2"
+                "inline-style-parser": "0.2.3"
             }
         },
         "node_modules/styled-components": {
@@ -31298,6 +31598,14 @@
                 "react-dom": ">= 16.8.0"
             }
         },
+        "node_modules/styled-components/node_modules/@emotion/is-prop-valid": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+            "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+            "dependencies": {
+                "@emotion/memoize": "^0.8.1"
+            }
+        },
         "node_modules/styled-components/node_modules/@emotion/unitless": {
             "version": "0.8.0",
             "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
@@ -31496,9 +31804,9 @@
             }
         },
         "node_modules/tar": {
-            "version": "6.2.0",
-            "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
-            "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+            "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
             "dependencies": {
                 "chownr": "^2.0.0",
                 "fs-minipass": "^2.0.0",
@@ -31527,9 +31835,9 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/terser": {
-            "version": "5.27.2",
-            "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz",
-            "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==",
+            "version": "5.30.0",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.0.tgz",
+            "integrity": "sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw==",
             "dependencies": {
                 "@jridgewell/source-map": "^0.3.3",
                 "acorn": "^8.8.2",
@@ -31609,7 +31917,8 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
@@ -31639,9 +31948,9 @@
             "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
         },
         "node_modules/tiny-invariant": {
-            "version": "1.3.1",
-            "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
-            "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
+            "version": "1.3.3",
+            "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+            "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
         },
         "node_modules/tinycolor2": {
             "version": "1.6.0",
@@ -32427,6 +32736,14 @@
             "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz",
             "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA=="
         },
+        "node_modules/tweakpane": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz",
+            "integrity": "sha512-BlcWOAe8oe4c+k9pmLBARGdWB6MVZMszayekkixQXTgkxTaYoTUpHpwVEp+3HkoamZkomodpbBf0CkguIHTgLg==",
+            "funding": {
+                "url": "https://github.com/sponsors/cocopon"
+            }
+        },
         "node_modules/tweetnacl": {
             "version": "0.14.5",
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@@ -32522,9 +32839,9 @@
             }
         },
         "node_modules/typed-array-length": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
-            "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz",
+            "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==",
             "dev": true,
             "dependencies": {
                 "call-bind": "^1.0.7",
@@ -32542,9 +32859,9 @@
             }
         },
         "node_modules/typescript": {
-            "version": "5.3.3",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
-            "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+            "version": "5.4.3",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
+            "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
             "bin": {
                 "tsc": "bin/tsc",
                 "tsserver": "bin/tsserver"
@@ -32934,11 +33251,11 @@
             "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
         },
         "node_modules/url/node_modules/qs": {
-            "version": "6.11.2",
-            "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
-            "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
+            "version": "6.12.0",
+            "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz",
+            "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==",
             "dependencies": {
-                "side-channel": "^1.0.4"
+                "side-channel": "^1.0.6"
             },
             "engines": {
                 "node": ">=0.6"
@@ -33105,9 +33422,9 @@
             }
         },
         "node_modules/victory-vendor": {
-            "version": "36.9.1",
-            "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.1.tgz",
-            "integrity": "sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==",
+            "version": "36.9.2",
+            "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+            "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
             "dependencies": {
                 "@types/d3-array": "^3.0.3",
                 "@types/d3-ease": "^3.0.0",
@@ -33169,9 +33486,9 @@
             }
         },
         "node_modules/watchpack": {
-            "version": "2.4.0",
-            "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
-            "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
+            "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==",
             "dependencies": {
                 "glob-to-regexp": "^0.4.1",
                 "graceful-fs": "^4.1.2"
@@ -33223,25 +33540,25 @@
             }
         },
         "node_modules/webpack": {
-            "version": "5.90.3",
-            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
-            "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
+            "version": "5.91.0",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz",
+            "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==",
             "dependencies": {
                 "@types/eslint-scope": "^3.7.3",
                 "@types/estree": "^1.0.5",
-                "@webassemblyjs/ast": "^1.11.5",
-                "@webassemblyjs/wasm-edit": "^1.11.5",
-                "@webassemblyjs/wasm-parser": "^1.11.5",
+                "@webassemblyjs/ast": "^1.12.1",
+                "@webassemblyjs/wasm-edit": "^1.12.1",
+                "@webassemblyjs/wasm-parser": "^1.12.1",
                 "acorn": "^8.7.1",
                 "acorn-import-assertions": "^1.9.0",
                 "browserslist": "^4.21.10",
                 "chrome-trace-event": "^1.0.2",
-                "enhanced-resolve": "^5.15.0",
+                "enhanced-resolve": "^5.16.0",
                 "es-module-lexer": "^1.2.1",
                 "eslint-scope": "5.1.1",
                 "events": "^3.2.0",
                 "glob-to-regexp": "^0.4.1",
-                "graceful-fs": "^4.2.9",
+                "graceful-fs": "^4.2.11",
                 "json-parse-even-better-errors": "^2.3.1",
                 "loader-runner": "^4.2.0",
                 "mime-types": "^2.1.27",
@@ -33249,7 +33566,7 @@
                 "schema-utils": "^3.2.0",
                 "tapable": "^2.1.1",
                 "terser-webpack-plugin": "^5.3.10",
-                "watchpack": "^2.4.0",
+                "watchpack": "^2.4.1",
                 "webpack-sources": "^3.2.3"
             },
             "bin": {
@@ -33340,13 +33657,14 @@
             }
         },
         "node_modules/webpack-dev-middleware": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.0.0.tgz",
-            "integrity": "sha512-tZ5hqsWwww/8DislmrzXE3x+4f+v10H1z57mA2dWFrILb4i3xX+dPhTkcdR0DLyQztrhF2AUmO5nN085UYjd/Q==",
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.0.tgz",
+            "integrity": "sha512-VLzmsjJrf+3UVf3QsT3E7xZ9F9bdhUhQFGRxyl6bmYR+W519UWnK6/teZeqAFFjWEtpVs+JqNPCVqB/s7P4tGg==",
             "dependencies": {
                 "colorette": "^2.0.10",
                 "memfs": "^4.6.0",
                 "mime-types": "^2.1.31",
+                "on-finished": "^2.4.1",
                 "range-parser": "^1.2.1",
                 "schema-utils": "^4.0.0"
             },
@@ -33367,9 +33685,9 @@
             }
         },
         "node_modules/webpack-dev-middleware/node_modules/memfs": {
-            "version": "4.7.7",
-            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.7.7.tgz",
-            "integrity": "sha512-x9qc6k88J/VVwnfTkJV8pRRswJ2156Rc4w5rciRqKceFDZ0y1MqsNL9pkg5sE0GOcDzZYbonreALhaHzg1siFw==",
+            "version": "4.8.0",
+            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.8.0.tgz",
+            "integrity": "sha512-fcs7trFxZlOMadmTw5nyfOwS3il9pr3y+6xzLfXNwmuR/D0i4wz6rJURxArAbcJDGalbpbMvQ/IFI0NojRZgRg==",
             "dependencies": {
                 "tslib": "^2.0.0"
             },
@@ -33382,9 +33700,9 @@
             }
         },
         "node_modules/webpack-dev-server": {
-            "version": "4.15.1",
-            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
-            "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
+            "version": "4.15.2",
+            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
+            "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
             "dev": true,
             "dependencies": {
                 "@types/bonjour": "^3.5.9",
@@ -33415,7 +33733,7 @@
                 "serve-index": "^1.9.1",
                 "sockjs": "^0.3.24",
                 "spdy": "^4.0.2",
-                "webpack-dev-middleware": "^5.3.1",
+                "webpack-dev-middleware": "^5.3.4",
                 "ws": "^8.13.0"
             },
             "bin": {
@@ -33507,9 +33825,9 @@
             }
         },
         "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": {
-            "version": "5.3.3",
-            "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
-            "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
+            "version": "5.3.4",
+            "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
+            "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
             "dev": true,
             "dependencies": {
                 "colorette": "^2.0.10",
@@ -33746,30 +34064,33 @@
             }
         },
         "node_modules/which-collection": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
-            "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+            "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
             "dev": true,
             "dependencies": {
-                "is-map": "^2.0.1",
-                "is-set": "^2.0.1",
-                "is-weakmap": "^2.0.1",
-                "is-weakset": "^2.0.1"
+                "is-map": "^2.0.3",
+                "is-set": "^2.0.3",
+                "is-weakmap": "^2.0.2",
+                "is-weakset": "^2.0.3"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
         "node_modules/which-typed-array": {
-            "version": "1.1.14",
-            "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz",
-            "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==",
+            "version": "1.1.15",
+            "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
+            "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
             "dependencies": {
-                "available-typed-arrays": "^1.0.6",
-                "call-bind": "^1.0.5",
+                "available-typed-arrays": "^1.0.7",
+                "call-bind": "^1.0.7",
                 "for-each": "^0.3.3",
                 "gopd": "^1.0.1",
-                "has-tostringtag": "^1.0.1"
+                "has-tostringtag": "^1.0.2"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -34173,9 +34494,9 @@
             }
         },
         "node_modules/zip-stream": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz",
-            "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==",
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz",
+            "integrity": "sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==",
             "dependencies": {
                 "archiver-utils": "^4.0.1",
                 "compress-commons": "^5.0.1",
diff --git a/package.json b/package.json
index b27d50d54..65b06f65c 100644
--- a/package.json
+++ b/package.json
@@ -130,7 +130,7 @@
         "@types/pdf-parse": "^1.1.4",
         "@types/reveal": "^4.2.0",
         "@types/supercluster": "^7.1.3",
-        "@types/web": "^0.0.138",
+        "@types/web": "^0.0.142",
         "@webscopeio/react-textarea-autocomplete": "^4.9.2",
         "adm-zip": "^0.5.10",
         "archiver": "^6.0.1",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index dbf9641b6..b06801066 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -357,7 +357,7 @@ pie title Minerals in my tap water
             {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100, _layout_fitWidth: true }},
             {key: "Webpage",    creator: opts => Docs.Create.WebDocument("",opts),          opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }},
             {key: "Comparison", creator: Docs.Create.ComparisonDocument,                    opts: { _width: 300, _height: 300 }},
-            {key: "Audio",      creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
+            {key: "Audio",      creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, _layout_fitWidth: true, }},
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index e84f0ed06..c685ec66f 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -607,7 +607,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             <div
                 className="audiobox-file"
                 style={{
-                    pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none',
                     flexDirection: this.miniPlayer ? 'row' : 'column',
                     justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
                 }}>
@@ -663,9 +662,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             max="1"
                             value={this._muted ? 0 : this._volume}
                             className="toolbar-slider volume"
-                            onPointerDown={(e: React.PointerEvent) => {
-                                e.stopPropagation();
-                            }}
+                            onPointerDown={e => e.stopPropagation()}
                             onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
                         />
                     </div>
@@ -692,8 +689,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                                 value={this.timeline?._zoomFactor ?? 1}
                                 className="toolbar-slider"
                                 id="zoom-slider"
-                                onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
-                                onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.zoom(Number(e.target.value))}
+                                onPointerDown={e => e.stopPropagation()}
+                                onChange={e => this.zoom(Number(e.target.value))}
                             />
                         </div>
                     )}
@@ -753,7 +750,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     render() {
         return (
-            <div ref={this.setupTimelineDrop} className="audiobox-container" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+            <div ref={this.setupTimelineDrop} className="audiobox-container" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none' }}>
                 {!this.path ? this.recordingControls : this.playbackControls}
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b815342e3..5c779734d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1023,7 +1023,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const state = this._editorView.state;
             const to = state.selection.to;
             const updated = TextSelection.create(state.doc, to, to);
-            this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
+            this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
             if (this._recordingDictation) {
                 this.recordDictation();
             }
@@ -1603,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 node = state.doc.nodeAt(xpos);
             } catch (e) {}
             if (node?.type !== schema.nodes.dashFieldView) {
-                editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
+                editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos))));
                 let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
                 while (target && !target.dataset?.targethrefs) target = target.parentElement;
                 FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-- 
cgit v1.2.3-70-g09d2


From aeeebf6d83868d4a5040e9632503511617242c55 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 16:13:31 -0400
Subject: updated and fixed wiki link to use @(wiki:...) and fixed linkManager
 from infinite looping on relatedlinker().  fixed clicking in text exxeptions.

---
 src/client/util/RTFMarkup.tsx                             |  2 +-
 src/client/views/nodes/LinkBox.tsx                        |  4 ++--
 src/client/views/nodes/LinkDocPreview.tsx                 |  5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/nodes/formattedText/RichTextRules.ts     | 12 ++++++------
 5 files changed, 13 insertions(+), 12 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 57485d893..35b1579df 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -30,7 +30,7 @@ export class RTFMarkup extends React.Component<{}> {
         return (
             <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
+                    <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
                 <p>
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 36bd037ca..3a2509c3d 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -48,9 +48,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         this._disposers.deleting = reaction(
-            () => (!this.anchor1 || !this.anchor2) && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
+            () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
             empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => 
-                (!this.anchor1 || !this.anchor2) && this._props.removeDocument?.(this.Document)
+                (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
             )), 1000) // prettier-ignore
         );
         this._disposers.dragging = reaction(
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4b6ee7d72..c9c8f9260 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -185,8 +185,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
         } else if (this._props.hrefs?.length) {
             const webDoc =
-                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()).lastElement() ??
-                Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
+                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
+                    .filter(doc => doc.type === DocumentType.WEB)
+                    .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
             DocumentManager.Instance.showDocument(webDoc, {
                 openLocation: OpenWhere.lightbox,
                 willPan: true,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5c779734d..66df1eaf2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1607,7 +1607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
                 while (target && !target.dataset?.targethrefs) target = target.parentElement;
                 FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-            } else {
+            } else if (node) {
                 try {
                     editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
                 } catch (e) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 483035e86..5e53a019e 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -295,7 +295,7 @@ export class RichTextRules {
 
             // create a hyperlink to a titled document
             // @(<doctitle>)
-            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@:\.\? \-0-9]+)\)/), (state, match, start, end) => {
+            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
                 const docTitle = match[2];
                 const prefixLength = '@('.length;
                 if (docTitle) {
@@ -380,17 +380,17 @@ export class RichTextRules {
             }),
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
-            // wiki:title
-            new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
-                const title = match[1];
+            // @(wiki:title)
+            new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+                const title = match[1].trim().replace(/ /g, '_');
                 this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
 
                 this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
 
                 const fstate = this.TextBox.EditorView?.state;
                 if (fstate) {
-                    const tr = fstate?.tr.deleteRange(start, start + 5);
-                    return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+                    const tr = fstate?.tr.deleteRange(start, start + '@(wiki:'.length);
+                    return tr.setSelection(new TextSelection(tr.doc.resolve(end - '@(wiki:'.length))).insertText(' ');
                 }
                 return state.tr;
             }),
-- 
cgit v1.2.3-70-g09d2


From 47ae05389d7ce564efde19b7a639b38842759cc1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 31 Mar 2024 00:09:33 -0400
Subject: fixed dropdown in title bar location.  moved rotate button up to not
 interfere with button bar.  don't blur title when editing it.  fix drawgging
 rotate center on templates.  support setting alternate colors for text docs
 -- pretty hacky.   fixed tabbing through dashFieldViews.

---
 src/client/views/DocumentDecorations.scss           |  2 +-
 src/client/views/DocumentDecorations.tsx            | 21 +++++++++++++++------
 src/client/views/StyleProvider.tsx                  |  5 ++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  2 +-
 src/client/views/global/globalScripts.ts            |  4 +++-
 src/client/views/nodes/DocumentView.scss            |  6 ++++++
 src/client/views/nodes/DocumentView.tsx             |  6 +++++-
 .../views/nodes/formattedText/DashFieldView.tsx     | 17 ++++++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  7 +++++--
 9 files changed, 50 insertions(+), 20 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ac0ef054c..239c0a977 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -14,7 +14,7 @@ $resizeHandler: 8px;
         height: 30;
         width: 30;
         right: -40;
-        bottom: -40;
+        bottom: -20;
         //top: calc(50% - 15px);
         position: absolute;
         pointer-events: all;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6698cd5bc..951e0912c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -73,11 +73,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         DocumentDecorations.Instance = this;
         document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
             action(e => {
+                let inputting = false;
+                if (this._titleControlString.startsWith('$')) {
+                    const titleFieldKey = this._titleControlString.substring(1);
+                    if (SelectionManager.Views[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
+                        inputting = true;
+                    }
+                }
                 const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
                 const {x,y} = Utils.rotPt(e.clientX - center.x, 
                                           e.clientY - center.y, 
                                           NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
-                (this._showNothing = !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
+                (this._showNothing = !inputting && !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
                     (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x || 
                      this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
             })); // prettier-ignore
@@ -336,8 +343,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
-        seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
-        seldocview.Document.rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+        seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+        seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
     };
 
     @action
@@ -350,8 +357,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
                 this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean, 
             action(e => (this._isRotating = false)), // upEvent
-            action(e => (seldocview.Document.rotation_centerX = seldocview.Document.rotation_centerY = 0))
+            action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)),
+            true
         ); // prettier-ignore
+        e.stopPropagation();
     };
 
     @action
@@ -617,7 +626,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (lastView) {
             const invXf = lastView.screenToContentsTransform().inverse();
             const seldoc = lastView.layoutDoc;
-            const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
+            const loccenter = Utils.rotPt(NumCast(seldoc._rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc._rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
         }
         return this._rotCenter;
@@ -721,7 +730,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     value={hideTitle ? '' : this._accumulatedTitle}
                     onBlur={action((e: React.FocusEvent) => {
                         this._editingTitle = false;
-                        !hideTitle && this.titleBlur();
+                        this.titleBlur();
                     })}
                     onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
                     onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 749198fe6..dcec2fe3d 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -200,7 +200,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
-            let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+            const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
+            const usePath = StrCast(doc?.[dataKey + "_usePath"]);
+            const alternate = usePath.includes(":hover") ? ( doc?.isHovering ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
+            let docColor: Opt<string> = StrCast(doc?.[fieldKey+alternate], StrCast(doc?.['backgroundColor' +alternate], isCaption ? 'rgba(0,0,0,0.4)' : ''));
             if (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
             // prettier-ignore
             switch (layoutDoc?.type) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d435173f3..791124f50 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1288,7 +1288,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const rotation = Cast(_rotation,'number',
             !this.layoutDoc._rotation_jitter ? null
                 : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
-        const childProps = { ...this._props, styleProvider: this.clusterStyleProvider };
+        const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
         return {
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3ea7bfbcd..dab642499 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -59,7 +59,9 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
                 obj[fieldKey] = color;
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                dv.Document[DocData][fieldKey] = color;
+                const dataKey = Doc.LayoutFieldKey(dv.Document);
+                const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
+                dv.dataDoc[fieldKey + alternate] = color;
             }
         });
     } else {
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 4fea6b3c6..23dada260 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -234,6 +234,12 @@
     }
 }
 
+.documntViewInternal-dropdown {
+    > div {
+        transform-origin: top left !important;
+    }
+}
+
 .contentFittingDocumentView {
     position: relative;
     display: flex;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a04030a5f..fc2da18d9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -843,7 +843,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     background,
                     pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
-                {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>}
+                {!dropdownWidth ? null : (
+                    <div className="documntViewInternal-dropdown" style={{ width: dropdownWidth }}>
+                        {this.fieldsDropdown(showTitle)}
+                    </div>
+                )}
                 <div
                     style={{
                         width: `calc(100% - ${dropdownWidth}px)`,
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f6ca97ee9..22a0cbe5e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -29,6 +29,7 @@ export class DashFieldView {
     root: any;
     node: any;
     tbox: FormattedTextBox;
+    getpos: any;
     @observable _nodeSelected = false;
     NodeSelected = () => this._nodeSelected;
 
@@ -38,6 +39,7 @@ export class DashFieldView {
         const self = this;
         this.node = node;
         this.tbox = tbox;
+        this.getpos = getPos;
         this.dom = document.createElement('div');
         this.dom.style.width = node.attrs.width;
         this.dom.style.height = node.attrs.height;
@@ -54,13 +56,13 @@ export class DashFieldView {
                 const editor = tbox.EditorView;
                 if (editor) {
                     const state = editor.state;
-                    for (var i = state.selection.to; i < state.doc.content.size; i++) {
+                    for (var i = self.getpos() + 1; i < state.doc.content.size; i++) {
                         if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
                             editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
                             return;
                         }
                     }
-                    tBox.setFocus(state.selection.to);
+                    // tBox.setFocus(state.selection.to);
                 }
             }
         };
@@ -137,7 +139,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
         this._textBoxDoc = this._props.tbox.Document;
-        const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc);
+        const setDoc = action((doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc));
 
         if (this._props.docId) {
             DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -255,7 +257,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
 
     @undoBatch
     selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
-        event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value);
+        event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value === '-unset-' ? undefined : event.target.value);
     };
 
     @computed get values() {
@@ -281,13 +283,14 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     </span>
                 )}
                 {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
-                {/* {!this.values.length ? null : (
-                    <select className="dashFieldView-select" tabIndex={-1} onChange={this.selectVal}>
+                {!this.values.length ? null : (
+                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={Field.toKeyValueString(this._dashDoc!, this._fieldKey)} onChange={this.selectVal}>
+                        <option value="-unset-">-unset-</option>
                         {this.values.map(val => (
                             <option value={val.value}>{val.label}</option>
                         ))}
                     </select>
-                )} */}
+                )}
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 66df1eaf2..3700b08d6 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2064,8 +2064,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
-                onPointerEnter={action(() => (this._isHovering = true))}
-                onPointerLeave={action(() => (this._isHovering = false))}
+                onPointerEnter={action(() => {
+                    this._isHovering = true;
+                    this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
+                })}
+                onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
                 ref={r => {
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                     this._oldWheel = r;
-- 
cgit v1.2.3-70-g09d2


From 1e37adacfe5b44a71ed7d7dfd785c6c2d0808eae Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 31 Mar 2024 08:43:38 -0400
Subject: fixed divider column for notetaking view.  fixed scrolling of text
 docs.  fixed keeping user docs in system button bars.

---
 src/client/documents/Documents.ts                            |  2 +-
 .../views/collections/CollectionNoteTakingViewDivider.tsx    | 12 ++++++++++++
 src/client/views/nodes/formattedText/FormattedTextBox.scss   |  1 +
 src/client/views/nodes/formattedText/FormattedTextBox.tsx    |  1 +
 4 files changed, 15 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8dccdeba9..6a0d45543 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1591,7 +1591,7 @@ export namespace DocUtils {
                 }
             });
             items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
-            items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
+            items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
         }
         return doc;
     }
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 5e4bce19d..50a97b978 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -51,6 +51,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
                     cursor: 'col-resize',
                     pointerEvents: this._props.isContentActive() ? 'all' : 'none',
                 }}>
+                <div
+                    className="columnResizer-handler"
+                    onPointerDown={e => this.registerResizing(e)}
+                    style={{
+                        height: '95%',
+                        width: 12,
+                        borderRight: '4px solid #282828',
+                        borderLeft: '4px solid #282828',
+                        position: 'fixed',
+                        pointerEvents: 'none',
+                    }}
+                />
                 <div
                     className="columnResizer-handler"
                     onPointerDown={e => this.registerResizing(e)}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 03ff0436b..00d890860 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -156,6 +156,7 @@ audiotag:hover {
 .formattedTextBox-inner,
 .formattedTextBox-inner-minimal {
     height: 100%;
+    overflow: auto;
     white-space: pre-wrap;
     .ProseMirror:hover {
         background: rgba(200, 200, 200, 0.2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3700b08d6..2deecb6ad 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2032,6 +2032,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const height = Number(styleFromLayoutString.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
+                !Number.isNaN(height) &&
                 (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
                 (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
-- 
cgit v1.2.3-70-g09d2


From 108e27f0d08a8d6d3b3fcd456d6cca999c077e8b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 31 Mar 2024 23:09:53 -0400
Subject: fixed scrolling to targets in text views.  fixed referencing fields
 on another doc from text box.  fixed '@name' technique for publishing
 documents to also remove documents and work anywhere a title is set.

---
 src/client/documents/Documents.ts                  |  5 ++++
 src/client/util/Scripting.ts                       |  2 +-
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.tsx           |  6 ----
 .../views/collections/CollectionDockingView.tsx    | 25 ++++++++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  8 ++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++++++++------------
 .../views/nodes/formattedText/RichTextRules.ts     | 13 +++------
 src/client/views/nodes/formattedText/nodes_rts.ts  |  1 -
 src/fields/Doc.ts                                  | 13 +++++++--
 src/fields/util.ts                                 | 16 +++++++++-
 12 files changed, 78 insertions(+), 49 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6a0d45543..eb15c332f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1516,6 +1516,9 @@ export namespace DocUtils {
             return linkDoc;
         });
 
+        const a = source.layout_unrendered ? 'link_anchor_1.annotationOn' : 'link_anchor_1';
+        const b = target.layout_unrendered ? 'link_anchor_2.annotationOn' : 'link_anchor_2';
+
         return makeLink(
             Docs.Create.LinkDocument(
                 source,
@@ -1529,6 +1532,8 @@ export namespace DocUtils {
                     link_displayLine: linkSettings.link_displayLine,
                     link_relationship: linkSettings.link_relationship,
                     link_description: linkSettings.link_description,
+                    x: ComputedField.MakeFunction(`(this.${a}.x+this.${b}.x)/2`) as any,
+                    y: ComputedField.MakeFunction(`(this.${a}.y+this.${b}.y)/2`) as any,
                     link_autoMoveAnchors: true,
                     _lockedPosition: true,
                     _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 31222aa50..422e708bc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -250,7 +250,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     const outputText = host.readFile('file.js');
 
     const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
-
+    if (script.startsWith('@')) options.typecheck = true; // need the compilation to fail so that the script will return itself as a string (instead of nothing)
     const result = Run(outputText, paramNames, diagnostics, script, options);
 
     if (options.globals) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index d65e0b406..15ce4c15f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -416,7 +416,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 )}
 
                 {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
-                {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>}
+                <div className="documentButtonBar-button">{this.templateButton}</div>
                 {!SelectionManager.Views?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 951e0912c..9e469ed1f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -131,12 +131,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     SelectionManager.Views.forEach(d => {
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
-                            if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemFromMyPublished(d.Document);
-                            }
-                            if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddToMyPublished(d.Document);
-                            }
                         }
                         KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
                     }),
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 25bfdb588..b2897a9b7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -3,14 +3,14 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
+import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
@@ -32,6 +32,7 @@ import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
+import { ComputedField } from '../../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() {
         }
     };
 
+    /**
+     * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs
+     * Once published, any text that uses the 'title' in its body will automatically
+     * be linked to this published document.
+     * @param target
+     * @param title
+     */
+    titleChanged = (target: any, value: any) => {
+        const title = Field.toString(value);
+        if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+            const embedding = DocListCast(target.proto_embeddings).lastElement();
+            embedding && Doc.AddToMyPublished(embedding);
+        } else if (!title.startsWith('@')) {
+            DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc));
+        }
+    };
+
     componentDidMount: () => void = async () => {
         this._unmounting = false;
+        SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
         if (this._containerRef.current) {
             this._lightboxReactionDisposer = reaction(
                 () => LightboxView.LightboxDoc,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6b3a56b0b..6eca91e9d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -499,7 +499,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             _layout_showSidebar: true,
             title: 'overview',
         });
-        const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+        const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
         DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
 
         portal.hidden = true;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 22a0cbe5e..763939a68 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -87,7 +87,6 @@ export class DashFieldView {
                 hideValue={node.attrs.hideValue}
                 editable={node.attrs.editable}
                 nodeSelected={this.NodeSelected}
-                dataDoc={node.attrs.dataDoc}
                 tbox={tbox}
             />
         );
@@ -119,7 +118,6 @@ interface IDashFieldViewInternal {
     height: number;
     editable: boolean;
     nodeSelected: () => boolean;
-    dataDoc: boolean;
     node: any;
     getPos: any;
     unclickable: () => boolean;
@@ -139,7 +137,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
         this._textBoxDoc = this._props.tbox.Document;
-        const setDoc = action((doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc));
+        const setDoc = action((doc: Doc) => (this._dashDoc = doc));
 
         if (this._props.docId) {
             DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -168,7 +166,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
             setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
         }
     });
-    selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
+    selectedCell = (): [Doc, number] | undefined => (this._dashDoc ? [this._dashDoc, 0] : undefined);
     columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey
 
     // set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -284,7 +282,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 )}
                 {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
                 {!this.values.length ? null : (
-                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={Field.toKeyValueString(this._dashDoc!, this._fieldKey)} onChange={this.selectVal}>
+                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={this._dashDoc && Field.toKeyValueString(this._dashDoc, this._fieldKey)} onChange={this.selectVal}>
                         <option value="-unset-">-unset-</option>
                         {this.values.map(val => (
                             <option value={val.value}>{val.label}</option>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2deecb6ad..80e56efe0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -478,7 +478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
             !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            (title.startsWith('-') || title.startsWith('@')) &&
+            title.startsWith('-') &&
             this._editorView &&
             !this.dataDoc.title_custom &&
             (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
@@ -486,14 +486,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
             const str = node.textContent;
-            const prefix = str.startsWith('@') ? '' : '-';
+            const prefix = '-';
 
             const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
-                if (str.startsWith('@') && str.length > 1) {
-                    Doc.AddToMyPublished(this.Document);
-                }
             }
         }
     };
@@ -510,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
-            const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
+            const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
             var alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 if (
@@ -1312,15 +1309,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
+                if (!this._ignoreScroll && this.ProseRef && !this._props.dontSelectOnLoad) {
                     const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
                     if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
+                        this._scrollStopper = smoothScroll(duration, this.ProseRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                     } else {
-                        this._scrollRef.current.scrollTo({ top: pos });
+                        this.ProseRef.scrollTo({ top: pos });
                     }
                 }
             },
@@ -1418,7 +1415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 handleScrollToSelection: editorView => {
                     const docPos = editorView.coordsAtPos(editorView.state.selection.to);
                     const viewRect = self._ref.current!.getBoundingClientRect();
-                    const scrollRef = self._scrollRef.current;
+                    const scrollRef = self.ProseRef;
                     const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
@@ -1750,20 +1747,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
         }
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
+
+        // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
+        const match = RTFCast(this.Document[this.fieldKey]).Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+        if (match) {
+            this.dataDoc.title_custom = true;
+            this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
+            this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
+        }
+
         this.endUndoTypingBatch();
 
         FormattedTextBox.LiveTextUndo?.end();
         FormattedTextBox.LiveTextUndo = undefined;
 
         const state = this._editorView!.state;
-        if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
-            UndoManager.RunInBatch(() => {
-                this.dataDoc.title_custom = true;
-                this.dataDoc.layout_showTitle = 'title';
-                const tr = this._editorView!.state.tr;
-                this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection());
-            }, 'titler');
-        }
         // if the text box blurs and none of its contents are focused(), then pass the blur along
         setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
     };
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 5e53a019e..e8cf9e992 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -295,8 +295,8 @@ export class RichTextRules {
 
             // create a hyperlink to a titled document
             // @(<doctitle>)
-            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
-                const docTitle = match[2];
+            new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+                const docTitle = match[1];
                 const prefixLength = '@('.length;
                 if (docTitle) {
                     const linkToDoc = (target: Doc) => {
@@ -342,12 +342,7 @@ export class RichTextRules {
                     const assign = match[4] === ':' ? (match[4] = '') : match[4];
                     const value = match[5];
                     const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
-                    const getTitledDoc = (docTitle: string) => {
-                        if (!DocServer.FindDocByTitle(docTitle)) {
-                            Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
-                        }
-                        return DocServer.FindDocByTitle(docTitle);
-                    };
+                    const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
                     // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
                     if (value?.includes(',') && !value.startsWith('((')) {
                         const values = value.split(',');
@@ -359,7 +354,7 @@ export class RichTextRules {
                         if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
                     }
                     const target = docTitle ? getTitledDoc(docTitle) : undefined;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false, dataDoc });
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
                 },
                 { inCode: true }
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index e335044ea..cab3a6ef5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -266,7 +266,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             hideKey: { default: false },
             hideValue: { default: false },
             editable: { default: true },
-            dataDoc: { default: false },
         },
         leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
         group: 'inline',
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 200896e25..9973232bf 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -86,7 +86,8 @@ export namespace Field {
         // this is a bit hacky, but we treat '^@' references to a published document
         // as a kind of macro to include the content of those documents
         Doc.MyPublishedDocs.forEach(doc => {
-            const regex = new RegExp(`^\\^${doc.title}\\s`, 'm');
+            const regexMultilineFlag = 'm';
+            const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[\(\)]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
             const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
             if (script.match(regex)) {
                 script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
@@ -218,8 +219,14 @@ export class Doc extends RefField {
     public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
     public static AddToMyOverlay(doc: Doc)    { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
     public static RemFromMyOverlay(doc: Doc)  { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
-    public static AddToMyPublished(doc: Doc)  { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
-    public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc)  {
+        doc[DocData].title_custom = true;
+        doc[DocData].layout_showTitle = 'title'; 
+        Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc){ 
+        doc[DocData].title_custom = false;
+        doc[DocData].layout_showTitle = undefined;
+        Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
     public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
 
     constructor(id?: FieldId, forceSave?: boolean) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c2ec3f13a..ad592391e 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,4 +1,4 @@
-import { $mobx, action, observable, runInAction, trace } from 'mobx';
+import { $mobx, action, observable, runInAction, trace, values } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { returnZero } from '../Utils';
 import { DocServer } from '../client/DocServer';
@@ -16,6 +16,7 @@ import { RichTextField } from './RichTextField';
 import { SchemaHeaderField } from './SchemaHeaderField';
 import { ComputedField } from './ScriptField';
 import { DocCast, ScriptCast, StrCast } from './Types';
+import { BaseException } from 'pdfjs-dist/types/src/shared/util';
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -56,6 +57,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         delete curValue[FieldChanged];
     }
 
+    if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value);
+
     const effectiveAcl = GetEffectiveAcl(target);
 
     const writeMode = DocServer.getFieldWriteMode(prop as string);
@@ -282,6 +285,17 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     dataDocChanged && updateCachedAcls(dataDoc);
 }
 
+export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+/**
+ * sets a callback function to be called whenever a value is assigned to the specified field key.
+ * For example, this is used to "publish" documents with titles that start with '@'
+ * @param prop
+ * @param setter
+ */
+export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) {
+    _propSetterCB.set(prop, setter);
+}
+
 //
 // target should be either a Doc or ListImpl.  receiver should be a Proxy<Doc> Or List.
 //
-- 
cgit v1.2.3-70-g09d2


From e4854bdb361f1d9e0ae85ee46327e2919117fc48 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 11:14:24 -0400
Subject: revert back to old style of forced text scrolling - doesn't work for
 pres trails.

---
 src/Utils.ts                                               |  5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.scss |  1 -
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  | 14 +++++++-------
 3 files changed, 10 insertions(+), 10 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 38325a463..21c91278f 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -653,13 +653,14 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen
     const animateScroll = () => {
         const currentDate = new Date().getTime();
         const currentTime = currentDate - startDate;
-        elements.map((element, i) => (element.scrollTop = easeFunc(transition, currentTime, starts[i], to - starts[i], duration)));
+        const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
+        elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
 
         if (!_stop) {
             if (currentTime < duration) {
                 requestAnimationFrame(animateScroll);
             } else {
-                elements.forEach(element => (element.scrollTop = to));
+                elements.forEach(element => setScrollTop(element, to));
             }
         }
     };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 00d890860..03ff0436b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -156,7 +156,6 @@ audiotag:hover {
 .formattedTextBox-inner,
 .formattedTextBox-inner-minimal {
     height: 100%;
-    overflow: auto;
     white-space: pre-wrap;
     .ProseMirror:hover {
         background: rgba(200, 200, 200, 0.2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 80e56efe0..a78d8363e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -91,7 +91,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _sidebarTagRef = React.createRef<React.Component>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _scrollRef: HTMLDivElement | null = null;
     private _editorView: Opt<EditorView>;
     public _applyingChange: string = '';
     private _inDrop = false;
@@ -1415,14 +1415,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 handleScrollToSelection: editorView => {
                     const docPos = editorView.coordsAtPos(editorView.state.selection.to);
                     const viewRect = self._ref.current!.getBoundingClientRect();
-                    const scrollRef = self.ProseRef;
+                    const scrollRef = self._scrollRef;
                     const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
                         const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
                         if (this._focusSpeed !== undefined) {
-                            scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
+                            setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
                         }
@@ -1823,10 +1823,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     };
     onScroll = (e: React.UIEvent) => {
-        if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
+        if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
             if (!this._props.dontSelectOnLoad) {
                 this._ignoreScroll = true;
-                this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
+                this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
                 this._ignoreScroll = false;
                 e.stopPropagation();
                 e.preventDefault();
@@ -2031,7 +2031,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
                 !Number.isNaN(height) &&
-                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+                (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
                 (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
@@ -2109,7 +2109,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     onDoubleClick={this.onDoubleClick}>
                     <div
                         className="formattedTextBox-outer"
-                        ref={this._scrollRef}
+                        ref={r => (this._scrollRef = r)}
                         style={{
                             width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
-- 
cgit v1.2.3-70-g09d2


From 5e865b0a96c7b13e538d159baea0c10416bef921 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 11:21:05 -0400
Subject: forgot these from last - fixes forced text scroll from pres trail.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a78d8363e..269a114bb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1309,15 +1309,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this.ProseRef && !this._props.dontSelectOnLoad) {
+                if (!this._ignoreScroll && this._scrollRef && !this._props.dontSelectOnLoad) {
                     const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
                     if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this.ProseRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
+                        this._scrollStopper = smoothScroll(duration, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                     } else {
-                        this.ProseRef.scrollTo({ top: pos });
+                        this._scrollRef.scrollTo({ top: pos });
                     }
                 }
             },
-- 
cgit v1.2.3-70-g09d2


From 83deb55fa4e99e25cebd4a9d4eb882c2fddee28b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 13:44:16 -0400
Subject: fixed header template to be marginally functional.

---
 src/Utils.ts                                       |  3 +-
 src/client/documents/Documents.ts                  |  7 ++--
 src/client/util/CurrentUserUtils.ts                | 25 +++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 42 +++++++++-------------
 src/fields/Doc.ts                                  |  3 +-
 5 files changed, 35 insertions(+), 45 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 21c91278f..a64c7c8a7 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -654,10 +654,9 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen
         const currentDate = new Date().getTime();
         const currentTime = currentDate - startDate;
         const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
-        elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
-
         if (!_stop) {
             if (currentTime < duration) {
+                elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
                 requestAnimationFrame(animateScroll);
             } else {
                 elements.forEach(element => setScrollTop(element, to));
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eb15c332f..20e74a3e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -254,9 +254,9 @@ export class DocumentOptions {
     dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
     _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
     undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
-    _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title', false);
-    _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
-    _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
+    _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
+    _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
+    _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
     _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
     _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
 
@@ -284,6 +284,7 @@ export class DocumentOptions {
     layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
     layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)');
     _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
+    _layout_autoHeightMargins?: NUMt = new NumInfo('Margin heights to be added to the computed auto height of a Doc');
     _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
     _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds', false);
     _layout_centered?: BOOLt = new BoolInfo('whether text should be vertically centered in Doc');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b06801066..16e0cb14a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -222,20 +222,19 @@ export class CurrentUserUtils {
         };
         const headerBtnHgt = 10;
         const headerTemplate = (opts:DocumentOptions) => {
-            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Untitled Header", 
+            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Header Template", 
                 layout:
-                "<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
-                `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
-                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' backgroundColor='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
-                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._layout_autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
-                "</HTMLdiv>"
+                `<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>
+                    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._header_height||0}px)'  />
+                    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._header_fontSize||9}px' height='{(this._header_height||0)}px' backgroundColor='{this._header_color || "lightGray"}' />
+                    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._header_height=Math.min(Math.max(0,this._height-30),this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${headerBtnHgt}:0)’} > Metadata</HTMLdiv>
+                </HTMLdiv>`
             }, "header");
-
             // "<div style={'height:100%'}>" +
-            // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
-            // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
+            // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._header_pointerEvents||`none`}' fontSize='{this._header_fontSize}px' height='{this._header_height}px' background='{this._header_color}' />" +
+            // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._header_height)*scale}px' height='calc({100/scale}% - {this._header_height}px)'/>" +
             // "</div>";
-            MakeTemplate(Doc.GetProto(header));
+            MakeTemplate(header);
             return header; 
         }
         const slideView = (opts:DocumentOptions) => {
@@ -243,9 +242,9 @@ export class CurrentUserUtils {
                     [
                         Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200,  isSystem: true }),
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
-                    ], {...opts, title: "Untitled Slide View"});
+                    ], {...opts, title: "Slide View Template"});
 
-            MakeTemplate(Doc.GetProto(slide));
+            MakeTemplate(slide);
             return slide; 
         }
         const plotlyApi = () => {
@@ -364,7 +363,7 @@ pie title Minerals in my tap water
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true,  waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
-            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
             {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 269a114bb..2e8444379 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -884,7 +884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             event: undoBatch(() => {
                 this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
                 this.Document.layout_fieldKey = 'layout_meta';
-                setTimeout(() => (this.layoutDoc._headerHeight = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+                setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
             }),
             icon: 'eye',
         });
@@ -1305,25 +1305,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             );
             if (this._recordingDictation) setTimeout(this.recordDictation);
         }
-        var quickScroll: string | undefined = '';
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this._scrollRef && !this._props.dontSelectOnLoad) {
-                    const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
-                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
-                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
-                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
-                    if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
-                    } else {
-                        this._scrollRef.scrollTo({ top: pos });
-                    }
+                if (!this._ignoreScroll && this._scrollRef) {
+                    const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]+)(m?)s/);
+                    const duration = Number(durationStr?.[1]) * (durationStr?.[2] ? 1 : 1000);
+                    this._scrollStopper = smoothScroll(duration || 0, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                 }
             },
             { fireImmediately: true }
         );
-        quickScroll = undefined;
         this.tryUpdateScrollHeight();
         setTimeout(this.tryUpdateScrollHeight, 250);
     }
@@ -1749,7 +1741,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
 
         // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
-        const match = RTFCast(this.Document[this.fieldKey]).Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+        const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
         if (match) {
             this.dataDoc.title_custom = true;
             this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
@@ -1824,13 +1816,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     onScroll = (e: React.UIEvent) => {
         if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
-            if (!this._props.dontSelectOnLoad) {
-                this._ignoreScroll = true;
-                this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
-                this._ignoreScroll = false;
-                e.stopPropagation();
-                e.preventDefault();
-            }
+            this._ignoreScroll = true;
+            this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
+            this._ignoreScroll = false;
+            e.stopPropagation();
+            e.preventDefault();
         }
     };
     tryUpdateScrollHeight = () => {
@@ -2026,7 +2016,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
         if (this._props.isContentActive()) {
             const scale = this._props.NativeDimScaling?.() || 1;
-            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
             const height = Number(styleFromLayoutString.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
@@ -2059,7 +2049,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
-        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
@@ -2111,7 +2101,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         className="formattedTextBox-outer"
                         ref={r => (this._scrollRef = r)}
                         style={{
-                            width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+                            width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
@@ -2128,8 +2118,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             }}
                         />
                     </div>
-                    {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
-                    {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+                    {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+                    {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
                     {this.audioHandle}
                     {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
                 </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9973232bf..48214cf25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1162,7 +1162,8 @@ export namespace Doc {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
-        return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+        const match = StrCast(templateLayoutString || Doc.Layout(doc).layout).match(/fieldKey={'([^']+)'}/);
+        return match?.[1] || ''; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
-- 
cgit v1.2.3-70-g09d2


From 75cbf3ffc1893d02b48ef65ec834e9ae597398bf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 20:23:24 -0400
Subject: fixed text toggle buttons to highlight based on selection.  enabled
 background colors for text lists.  cleaned up text insertion point setting on
 pointer up.  fixed autoHeight for text boxes with 'auto' nodes like ordered
 lists.

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/views/global/globalScripts.ts           |  50 +++---
 .../views/nodes/formattedText/DashFieldView.scss   |   1 +
 .../nodes/formattedText/FormattedTextBox.scss      |  14 ++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  47 ++---
 .../formattedText/ProsemirrorExampleTransfer.ts    |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 199 ++++-----------------
 .../views/nodes/formattedText/RichTextRules.ts     |   4 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |  30 ++--
 9 files changed, 113 insertions(+), 236 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b73111062..38ebc86e7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -725,7 +725,7 @@ pie title Minerals in my tap water
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
             { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index dab642499..497ab98d8 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -151,26 +151,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
             checkResult: () => RichTextMenu.Instance?.fontFamily,
-            setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+            setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
         }],
         ['highlight', {
-            checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
-            setDoc: () => value  && RichTextMenu.Instance.setHighlight(value),
+            checkResult: () => RichTextMenu.Instance?.fontHighlight,
+            setDoc: () => value  && RichTextMenu.Instance?.setHighlight(value),
         }],
         ['fontColor', {
             checkResult: () => RichTextMenu.Instance?.fontColor,
-            setDoc: () => value && RichTextMenu.Instance.setColor(value),
+            setDoc: () => value && RichTextMenu.Instance?.setColor(value),
         }],
         ['alignment', {
-            checkResult: () => RichTextMenu.Instance.textAlign,
-            setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+            checkResult: () => RichTextMenu.Instance?.textAlign,
+            setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
         }],
         ['fontSize', {
             checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
             setDoc: () => {
                 if (typeof value === 'number') value = value.toString();
                 if (value && Number(value).toString() === value) value += 'px';
-                RichTextMenu.Instance.setFontSize(value);
+                RichTextMenu.Instance?.setFontSize(value);
             },
         }],
     ]);
@@ -182,45 +182,45 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
 });
 
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
-type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
 
 ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
     const textView = RichTextMenu.Instance?.TextView;
     const editorView = textView?.EditorView;
     // prettier-ignore
     const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
-        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance.textVcenter:
-                                                                              (RichTextMenu.Instance.textAlign === where)): 
+        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance?.textVcenter ?? false:
+                                                                              (RichTextMenu.Instance?.textAlign === where)): 
                                                          where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
                                                                               (Doc.UserDoc().textAlign ===where) ? true:false),
-                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
-                                                                              RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
+                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
+                                                                              RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
                                                             where === 'vcent' ?  Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
                                                                               (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
     // prettier-ignore
     const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
-        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
-                        toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance?.getActiveListStyle() === list:false),
+                        toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
     // prettier-ignore
     const attrs:attrfuncs[] = [
         ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
                         toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
         ['elide',      { checkResult: () => false,
                         toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
-        ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+        ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
-        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
-        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
-        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
         return map.get(charStyle)?.checkResult();
     }
-    undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
+    undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
 });
 
 export function checkInksToGroup() {
@@ -448,10 +448,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
  */
 ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
     SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
     }
-    if (editorView) RichTextMenu.Instance.setFontFamily(key);
+    if (editorView) RichTextMenu.Instance?.setFontFamily(key);
     else Doc.UserDoc().fontFamily = key;
 });
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 74eeb014c..d79df4272 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -26,6 +26,7 @@
         display: inline-block;
         font-weight: normal;
         background: rgba(0, 0, 0, 0.1);
+        cursor: default;
     }
     .dashFieldView-fieldSpan {
         min-width: 8px;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 03ff0436b..3dcc45c96 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -273,6 +273,7 @@ footnote::before {
     height: 20px;
     &::before {
         content: '→';
+        cursor: default;
     }
     &:hover {
         background: orange;
@@ -348,6 +349,7 @@ footnote::before {
     touch-action: none;
     span {
         font-family: inherit;
+        background-color: inherit;
     }
 
     blockquote {
@@ -397,6 +399,7 @@ footnote::before {
             font-family: inherit;
         }
         margin-left: 0;
+        background-color: inherit;
     }
     .decimal2-ol {
         counter-reset: deci2;
@@ -406,6 +409,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.1em;
+        background-color: inherit;
     }
     .decimal3-ol {
         counter-reset: deci3;
@@ -415,6 +419,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.85em;
+        background-color: inherit;
     }
     .decimal4-ol {
         counter-reset: deci4;
@@ -424,6 +429,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 3.85em;
+        background-color: inherit;
     }
     .decimal5-ol {
         counter-reset: deci5;
@@ -432,6 +438,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
     .decimal6-ol {
         counter-reset: deci6;
@@ -440,6 +447,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
     .decimal7-ol {
         counter-reset: deci7;
@@ -448,6 +456,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
 
     .multi1-ol {
@@ -458,6 +467,7 @@ footnote::before {
         }
         margin-left: 0;
         padding-left: 1.2em;
+        background-color: inherit;
     }
     .multi2-ol {
         counter-reset: multi2;
@@ -467,6 +477,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2em;
+        background-color: inherit;
     }
     .multi3-ol {
         counter-reset: multi3;
@@ -476,6 +487,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.85em;
+        background-color: inherit;
     }
     .multi4-ol {
         counter-reset: multi4;
@@ -485,6 +497,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 3.85em;
+        background-color: inherit;
     }
 
     //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
@@ -788,6 +801,7 @@ footnote::before {
         height: 20px;
         &::before {
             content: '→';
+            cursor: default;
         }
         &:hover {
             background: orange;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2e8444379..c2f3a6e4b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -291,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             });
         };
         AnchorMenu.Instance.Highlight = undoable((color: string) => {
-            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+            this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
             return undefined;
         }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -1577,32 +1577,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         document.removeEventListener('pointerup', this.onSelectEnd);
     };
     onPointerUp = (e: React.PointerEvent): void => {
-        const editor = this._editorView!;
-        const state = editor?.state;
-        if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
-        if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this._props.isContentActive() && !e.button) {
-            const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
-            let xpos = pcords?.pos || 0;
-            while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
-                xpos = xpos - 1;
-            }
-            let node: any;
-            try {
-                node = state.doc.nodeAt(xpos);
-            } catch (e) {}
-            if (node?.type !== schema.nodes.dashFieldView) {
-                editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos))));
-                let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
-                while (target && !target.dataset?.targethrefs) target = target.parentElement;
-                FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-            } else if (node) {
-                try {
-                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
-                } catch (e) {
-                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1))));
-                }
-            }
+        const state = this.EditorView?.state;
+        if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
+            if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+            let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+            for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+            while (target && !target.dataset?.targethrefs) target = target.parentElement;
+            FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
         }
     };
     @action
@@ -1788,7 +1769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
                 (document.activeElement as any).blur?.();
                 SelectionManager.DeselectAll();
-                RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
+                RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
                 return;
             case 'Enter':
                 this.insertTime();
@@ -1827,12 +1808,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children && !SnappingManager.IsDragging) {
-            const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
-            const toHgt = (node: Element) => {
+            const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
+            const toNum = (val: string) => Number(val.replace('px', ''));
+            const toHgt = (node: Element): number => {
                 const { height, marginTop, marginBottom } = getComputedStyle(node);
-                return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
+                const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height);
+                return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
             };
-            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
+            const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children);
             const scrollHeight = this.ProseRef && proseHeight;
             if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 92cc65fc5..ec8879487 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -104,7 +104,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                 !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
                     const tx25 = updateBullets(tx2, schema);
                     const ol_node = tx25.doc.nodeAt(range!.start)!;
-                    const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, { ...ol_node.attrs, ...(marks?.[0]?.type === schema.marks.pFontSize ? { fontSize: marks[0].attrs.fontSize } : {}) });
+                    const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
                     // when promoting to a list, assume list will format things so don't copy the stored marks.
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9b6eb8b8d..9c282a1d2 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -27,7 +27,10 @@ const { toggleMark } = require('prosemirror-commands');
 
 @observer
 export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
-    @observable static Instance: RichTextMenu;
+    static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
+    static get Instance() {
+        return RichTextMenu._instance?.menu;
+    }
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
     private _linkToRef = React.createRef<HTMLInputElement>();
 
@@ -67,7 +70,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     constructor(props: AntimodeMenuProps) {
         super(props);
         makeObservable(this);
-        RichTextMenu.Instance = this;
+        RichTextMenu._instance.menu = this;
         this.updateMenu(undefined, undefined, props, this.layoutDoc);
         this._canFade = false;
         this.Pinned = true;
@@ -131,11 +134,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
         }
 
-        // update active marks
-        const activeMarks = this.getActiveMarksOnSelection();
-        this.setActiveMarkButtons(activeMarks);
-
-        // update active font family and size
+        this.setActiveMarkButtons(this.getActiveMarksOnSelection());
         const active = this.getActiveFontStylesOnSelection();
         const activeFamilies = active.activeFamilies;
         const activeSizes = active.activeSizes;
@@ -164,15 +163,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             const fromRange = numberRange(state.selection.from).reverse();
             const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
             const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
-            if (node?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to))));
-            }
-            {
+            if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
+                const hasMark = node.marks.some(m => m.type === mark.type);
+                const otherMarks = node.marks.filter(m => m.type !== mark.type);
+                const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
+                const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
+                dispatch(updateBullets(markup, state.schema));
+            } else {
                 const state = this.view?.state;
                 const tr = this.view?.state.tr;
                 if (tr && state) {
@@ -225,11 +222,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         if (this.view && this.TextView?._props.rootSelected?.()) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
-            const marks: Mark[] = [...(state.storedMarks ?? [])];
+            var marks: Mark[] = [...(state.storedMarks ?? [])];
             if (state.storedMarks !== null) {
             } else if (state.selection.empty) {
-                const ref_node = this.reference_node(pos);
-                marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
+                for (let i = 0; i <= pos.depth; i++) {
+                    marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
+                }
             } else {
                 state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
                     node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
@@ -256,41 +254,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
-        let activeMarks: MarkType[] = [];
-        if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks;
+        if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
 
-        const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
-        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
-        //current selection
-        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
         const state = this.view.state;
-        if (!empty) {
-            activeMarks = markGroup.filter(mark => {
-                const has = false;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
-                }
-                return false;
-            });
-        } else {
-            const pos = this.view.state.selection.$from;
-            const ref_node: ProsNode | null = this.reference_node(pos);
-            if (ref_node !== null && ref_node !== this.view.state.doc) {
-                if (ref_node.isText) {
-                } else {
-                    return [];
-                }
-                activeMarks = markGroup.filter(mark_type => {
-                    // if (mark_type === state.schema.marks.pFontSize) {
-                    //     return mark.isINSet
-                    //     ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    // }
-                    const mark = state.schema.mark(mark_type);
-                    return mark.isInSet(ref_node.marks);
-                });
+        var marks: Mark[] = [...(state.storedMarks ?? [])];
+        const pos = this.view.state.selection.$from;
+        if (state.storedMarks !== null) {
+        } else if (state.selection.empty) {
+            for (let i = 0; i <= pos.depth; i++) {
+                marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
             }
+        } else {
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+                node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
+            });
         }
-        return activeMarks;
+        const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+        return markGroup.filter(mark_type => {
+            const mark = state.schema.mark(mark_type);
+            return mark.isInSet(marks);
+        });
     }
 
     @action
@@ -418,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // TODO: remove doesn't work
     // remove all node type and apply the passed-in one to the selected text
     changeListType = (mapStyle: string) => {
-        const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+        const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
         const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
         if (!this.view || nodeType?.attrs.mapStyle === '') return;
 
@@ -566,7 +549,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // todo: add brushes to brushMap to save with a style name
     onBrushNameKeyPress = (e: React.KeyboardEvent) => {
         if (e.key === 'Enter') {
-            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+            RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
             this._brushNameRef.current!.style.background = 'lightGray';
         }
     };
@@ -574,7 +557,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @action
     clearBrush() {
-        RichTextMenu.Instance.brushMarks = new Set();
+        RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set());
     }
 
     @action
@@ -710,118 +693,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         }
     };
 
-    linkExtend($start: ResolvedPos, href: string) {
-        const mark = this.view!.state.schema.marks.linkAnchor;
-
-        let startIndex = $start.index();
-        let endIndex = $start.indexAfter();
-
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
-
-        let startPos = $start.start();
-        let endPos = startPos;
-        for (let i = 0; i < endIndex; i++) {
-            const size = $start.parent.child(i).nodeSize;
-            if (i < startIndex) startPos += size;
-            endPos += size;
-        }
-        return { from: startPos, to: endPos };
-    }
-
-    reference_node(pos: ResolvedPos): ProsNode | null {
-        if (!this.view) return null;
-
-        let ref_node: ProsNode = this.view.state.doc;
-        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-            ref_node = pos.nodeBefore;
-        }
-        if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-            if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) {
-                ref_node = pos.nodeAfter;
-            }
-        }
-        if (!ref_node && pos.pos > 0) {
-            let skip = false;
-            for (let i: number = pos.pos - 1; i > 0; i--) {
-                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
-                    if (node.isLeaf && !skip) {
-                        ref_node = node;
-                        skip = true;
-                    }
-                });
-            }
-        }
-        if (!ref_node.isLeaf && ref_node.childCount > 0) {
-            ref_node = ref_node.child(0);
-        }
-        return ref_node;
-    }
-
     render() {
         return null;
-        // TraceMobx();
-        // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
-        //     //!this.collapsed ? this.getDragger() : (null),
-        //     // !this.Pinned ? (null) : <div key="frag1"> {[
-        //     //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-        //     //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-        //     //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-        //     //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-        //     //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-        //     //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-        //     //     <div className="richTextMenu-divider" key="divider" />
-        //     // ]}</div>,
-        //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-        //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-        //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-        //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-        //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-        //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-        //     this.createColorButton(),
-        //     this.createHighlighterButton(),
-        //     this.createLinkButton(),
-        //     this.createBrushButton(),
-        //     <div className="collectionMenu-divider" key="divider 2" />,
-        //     this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
-        //     this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
-        //     this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
-        //     this.createButton("indent", "Inset More", undefined, this.insetParagraph),
-        //     this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
-        //     this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
-        //     this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
-        // ]}</div>;
-
-        // const row2 = <div className="antimodeMenu-row row-2" key="row2">
-        //     {this.collapsed ? this.getDragger() : (null)}
-        //     <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
-        //         <div className="collectionMenu-divider" key="divider 3" />
-        //         {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
-        //             this.activeFontSize = val;
-        //             SelectionManager.Views.map(dv => dv.Document._text_fontSize = val);
-        //         })),
-        //         this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
-        //             this.activeFontFamily = val;
-        //             SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val);
-        //         })),
-        //         <div className="collectionMenu-divider" key="divider 4" />,
-        //         this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
-        //         this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
-        //         this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
-        //         this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
-        //         ]}
-        //     </div>
-        //     {/* <div key="collapser">
-        //         {<div key="collapser">
-        //             <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-        //                 <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
-        //             </button>
-        //         </div> }
-        //         <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-        //             <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-        //         </button>
-        //     </div> */}
-        // </div>;
     }
 }
 
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e8cf9e992..42665830f 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -246,7 +246,7 @@ export class RichTextRules {
             // activate a style by name using prefix '%<color name>'
             new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
-                const marks = RichTextMenu.Instance._brushMap.get(color);
+                const marks = RichTextMenu.Instance?._brushMap.get(color);
 
                 if (
                     DocListCast((Doc.UserDoc().template_notes as Doc).data)
@@ -367,7 +367,7 @@ export class RichTextRules {
                     if (count) {
                         const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
                         tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length))));
-                        RichTextMenu.Instance.elideSelection(this.TextBox.EditorView?.state, true);
+                        RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true);
                     }
                     count++;
                 });
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index cab3a6ef5..ceafc3ba1 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -24,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     // :: NodeSpec The top level document node.
     doc: {
         content: 'block+',
+        marks: '_',
     },
 
     paragraph: ParagraphNodeSpec,
@@ -331,12 +332,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         ...orderedList,
         content: 'list_item+',
         group: 'block',
+        marks: '_',
         attrs: {
             bulletStyle: { default: 0 },
-            mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
-            fontColor: { default: 'inherit' },
-            fontSize: { default: undefined },
-            fontFamily: { default: undefined },
+            mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet",
             visibility: { default: true },
             indent: { default: undefined },
         },
@@ -376,9 +375,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         ],
         toDOM(node: Node) {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
-            const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
-            const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
-            const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
             const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
             if (node.attrs.mapStyle === 'bullet') {
                 return [
@@ -386,7 +386,7 @@ export const nodes: { [index: string]: NodeSpec } = {
                     {
                         'data-mapStyle': node.attrs.mapStyle,
                         'data-bulletStyle': node.attrs.bulletStyle,
-                        style: `${fsize} ${ffam} ${fcol} ${marg}`,
+                        style: `${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
                     },
                     0,
                 ];
@@ -398,7 +398,7 @@ export const nodes: { [index: string]: NodeSpec } = {
                           class: `${map}-ol`,
                           'data-mapStyle': node.attrs.mapStyle,
                           'data-bulletStyle': node.attrs.bulletStyle,
-                          style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+                          style: `list-style: none; ${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
                       },
                       0,
                   ]
@@ -422,16 +422,22 @@ export const nodes: { [index: string]: NodeSpec } = {
                 },
             },
         ],
-        toDOM(node: any) {
+        toDOM(node: Node) {
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
             return [
                 'li',
-                { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+                { class: `${map}`, style: `${fhigh} ${fsize} ${ffam} ${fcol} `, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
                 node.attrs.visibility
                     ? 0
                     : [
                           'span',
-                          { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+                          {
+                              style: `${fhigh} ${fsize} ${ffam} ${fcol} position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre`,
+                          },
                           `${node.firstChild?.textContent}...`,
                       ],
             ];
-- 
cgit v1.2.3-70-g09d2

