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

---
 .../views/nodes/formattedText/DashFieldView.tsx    | 45 ++++++++++++++--------
 1 file changed, 30 insertions(+), 15 deletions(-)

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

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>
+            </>
         );
     }
 }
-- 
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/DashFieldView.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 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/DashFieldView.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 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/DashFieldView.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 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/DashFieldView.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 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/DashFieldView.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 6227ac6f13ba112fc6edac340940f4543d2ac82f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 14:19:03 -0400
Subject: restored pivot view for metadata not just tags.

---
 src/client/views/nodes/formattedText/DashFieldView.tsx | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 763939a68..439d4785e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -332,13 +332,11 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     render() {
         return this.getElement(
             <>
-                {!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>
-                )}
+                <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}>
-- 
cgit v1.2.3-70-g09d2

