From 724c1c1228aa601fdbbed93dde68a5a9fa9c5ae9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Jul 2022 09:12:11 -0400
Subject: added a 'guest' login mode.  added ability for mainView to be any
 doc, not just a docking collection.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cfaa428f9..e945920da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -640,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
-- 
cgit v1.2.3-70-g09d2


From 8f0752368e4c5831b909050940dff42171d51ae7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 21 Jul 2022 13:28:25 -0400
Subject: fixed undo for opening/closing sidebar for pdf/web/text.   fixed
 lightboxview for pdf's with smaller heights.

---
 src/client/documents/Documents.ts                         |  2 +-
 src/client/views/DocumentDecorations.tsx                  |  4 +++-
 src/client/views/nodes/DocumentView.tsx                   |  4 +++-
 src/client/views/nodes/PDFBox.tsx                         |  7 +++++--
 src/client/views/nodes/WebBox.tsx                         | 13 ++++++++++---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 13 ++++++++++++-
 6 files changed, 34 insertions(+), 9 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ed4c99d70..51a9283f4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -465,7 +465,7 @@ export namespace Docs {
                 DocumentType.AUDIO,
                 {
                     layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { _height: 100, backgroundColor: 'lightGray', forceReflow: true, nativeDimModifiable: true, links: '@links(self)' },
+                    options: { _height: 100, backgroundColor: 'lightGray', _fitWidth: true, forceReflow: true, nativeDimModifiable: true, links: '@links(self)' },
                 },
             ],
             [
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c55daca3f..780dcfb6d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -494,7 +494,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     if (fixedAspect) {
                         if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
                             if (dragRight && modifyNativeDim) {
-                                doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
+                                if (Doc.NativeWidth(doc)) {
+                                    doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
+                                }
                             } else {
                                 if (!doc._fitWidth) {
                                     actualdH = (nheight / nwidth) * actualdW;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dea718a0d..3a8552325 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1492,7 +1492,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0;
     }
     @computed get Yshift() {
-        return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && !this.layoutDoc.nativeHeightUnfrozen ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0;
+        return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && (!this.layoutDoc.nativeHeightUnfrozen || (!this.fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
+            ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
+            : 0;
     }
     @computed get centeringX() {
         return this.props.dontCenter?.includes('x') ? 0 : this.Xshift;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f41f6a1ad..fe18abba9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -302,8 +302,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }
                 return false;
             },
-            () => batch.end(),
-            () => this.toggleSidebar()
+            (e, movement, isClick) => !isClick && batch.end(),
+            () => {
+                this.toggleSidebar();
+                batch.end();
+            }
         );
     };
     @observable _previewNativeWidth: Opt<number> = undefined;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d97277c2b..05da8bd7b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -15,7 +15,7 @@ import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupM
 import { Docs, DocUtils } from '../../documents/Documents';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
@@ -708,6 +708,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     @observable _draggingSidebar = false;
     sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => {
+        const batch = UndoManager.StartBatch('sidebar');
         // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
         setupMoveUpEvents(
             this,
@@ -730,8 +731,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }
                 return false;
             }),
-            action(() => (this._draggingSidebar = false)),
-            () => this.toggleSidebar()
+            action((e, movement, isClick) => {
+                this._draggingSidebar = false;
+                !isClick && batch.end();
+            }),
+            () => {
+                this.toggleSidebar();
+                batch.end();
+            }
         );
     };
     @observable _previewNativeWidth: Opt<number> = undefined;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e945920da..383c22ba8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -640,7 +640,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
+        const batch = UndoManager.StartBatch('sidebar');
+        setupMoveUpEvents(
+            this,
+            e,
+            this.sidebarMove,
+            (e, movement, isClick) => !isClick && batch.end(),
+            () => {
+                this.toggleSidebar();
+                batch.end();
+            },
+            true
+        );
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
-- 
cgit v1.2.3-70-g09d2


From 358f9e266ef264442aea1e2c7d5d959a19f7624c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 22 Jul 2022 14:11:30 -0400
Subject: adjusted native dim scaling slightly combining props.scaling and
 props.ContentScaling into props.NativeDimScaling and fixing some resizing
 behaviors for fitWidth freeformviews and native-sized text boxes.   Also
 fixed clicking on presboxe elements to not drag.

---
 src/client/views/DocumentDecorations.tsx           |   3 +-
 src/client/views/InkingStroke.tsx                  | 536 +++++++++++++--------
 src/client/views/SidebarAnnos.tsx                  |   4 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../collections/CollectionStackedTimeline.tsx      |   2 +-
 .../views/collections/CollectionTreeView.tsx       |  11 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  30 +-
 .../CollectionMulticolumnView.tsx                  | 187 +++----
 .../CollectionMultirowView.tsx                     | 180 +++----
 src/client/views/nodes/DocumentContentsView.tsx    |   5 +-
 src/client/views/nodes/DocumentLinksButton.tsx     | 384 ++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  24 +-
 src/client/views/nodes/EquationBox.tsx             |  90 ++--
 src/client/views/nodes/FieldView.tsx               |  55 ++-
 src/client/views/nodes/ImageBox.tsx                |  10 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   9 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |  12 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   4 +-
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/WebBox.tsx                  |  25 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  10 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |   8 +-
 src/client/views/pdf/PDFViewer.tsx                 |  17 +-
 25 files changed, 883 insertions(+), 741 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 780dcfb6d..964fd36c8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -521,7 +521,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             } else doc._height = actualdH;
                         }
                     } else {
-                        dH && (doc._height = actualdH);
+                        const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight)) * docView.NativeDimScaling();
+                        dH && (doc._height = actualdH > maxHeight ? maxHeight : actualdH);
                         dW && (doc._width = actualdW);
                         dH && (doc._autoHeight = false);
                     }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index dace9716a..e5de7a0c5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,35 +20,37 @@
     
     Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
 */
-import React = require("react");
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, WidthSym } from "../../fields/Doc";
-import { InkData, InkField, InkTool } from "../../fields/InkField";
-import { Cast, NumCast, RTFCast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { SnappingManager } from "../util/SnappingManager";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
-import { Colors } from "./global/globalEnums";
-import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { InkTangentHandles } from "./InkTangentHandles";
-import { DocComponentView } from "./nodes/DocumentView";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-import "./InkStroke.scss";
-import Color = require("color");
+import React = require('react');
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, WidthSym } from '../../fields/Doc';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
+import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { TraceMobx } from '../../fields/util';
+import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { SnappingManager } from '../util/SnappingManager';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { ContextMenu } from './ContextMenu';
+import { ViewBoxBaseComponent } from './DocComponent';
+import { Colors } from './global/globalEnums';
+import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { InkTangentHandles } from './InkTangentHandles';
+import { DocComponentView } from './nodes/DocumentView';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import './InkStroke.scss';
+import Color = require('color');
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
-    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+    public static LayoutString(fieldStr: string) {
+        return FieldView.LayoutString(InkingStroke, fieldStr);
+    }
     public static IsClosed(inkData: InkData) {
         return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
     }
@@ -56,13 +58,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     private _selDisposer?: IReactionDisposer;
 
     @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
-    @observable _nearestT?: number;  // nearest t value within the nearest Bezier segment "
-    @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke ""
+    @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+    @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
         this.props.setContentView?.(this);
-        this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
-            selected => !selected && (InkStrokeProperties.Instance._controlButton = false));
+        this._selDisposer = reaction(
+            () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+            selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+        );
     }
     componentWillUnmount() {
         this._selDisposer?.();
@@ -70,36 +74,36 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
     // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1);
+    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
 
     getAnchor = () => {
         console.log(document.activeElement);
         return this._subContentView?.getAnchor?.() || this.rootDoc;
-    }
+    };
 
     scrollFocus = (textAnchor: Doc, smooth: boolean) => {
         return this._subContentView?.scrollFocus?.(textAnchor, smooth);
-    }
+    };
     /**
-    * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); 
-    * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
-    * the center of the ink stroke changes as the stroke is rotated.
-    */
+     * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
+     * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
+     * the center of the ink stroke changes as the stroke is rotated.
+     */
     getCenter = (xf: Transform) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const angle = -NumCast(this.layoutDoc.rotation);
         const newPoints = inkData.map(pt => {
-            const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX;
-            const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y;
+            const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX;
+            const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y;
             return { X: newX, Y: newY };
         });
         const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
         const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
-        const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX;
-        const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry;
+        const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX;
+        const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry;
         const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
         return { X: tc[0], Y: tc[1] };
-    }
+    };
 
     /**
      * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
@@ -107,7 +111,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     analyzeStrokes() {
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]);
     }
 
     /**
@@ -115,12 +119,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      *    When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will
      *  appear to illuminate what it covers up.  At the same time, all pixels that are not under the stroke will be
      *  dimmed by a semi-opaque overlay mask.
-    */
+     */
     public static toggleMask = action((inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
-        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
-        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
-        inkDoc.color = "#9b9b9bff";
+        inkDoc._backgroundColor = inkDoc.isInkMask ? 'rgba(0,0,0,0.7)' : undefined;
+        inkDoc.mixBlendMode = inkDoc.isInkMask ? 'hard-light' : undefined;
+        inkDoc.color = '#9b9b9bff';
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
     /**
@@ -132,46 +136,60 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         this._handledClick = false;
         const inkView = this.props.docViewPath().lastElement();
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
-            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
-            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+        const screenPts = inkData
+            .map(point =>
+                this.screenToLocal()
+                    .inverse()
+                    .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+            )
+            .map(p => ({ X: p[0], Y: p[1] }));
         const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
         const controlIndex = nearestSeg;
         const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
         var controlUndo: UndoManager.Batch | undefined;
         const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
-        setupMoveUpEvents(this, e,
-            !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
-                if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
-                const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
-                const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
-                InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
-                InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
-                return false;
-            }),
-            !isEditing ? returnFalse : action(() => {
-                controlUndo?.end();
-                controlUndo = undefined;
-                UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
-            }),
+        setupMoveUpEvents(
+            this,
+            e,
+            !isEditing
+                ? returnFalse
+                : action((e: PointerEvent, down: number[], delta: number[]) => {
+                      if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+                      const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
+                      const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
+                      InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+                      InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+                      return false;
+                  }),
+            !isEditing
+                ? returnFalse
+                : action(() => {
+                      controlUndo?.end();
+                      controlUndo = undefined;
+                      UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+                  }),
             action((e: PointerEvent, doubleTap: boolean | undefined) => {
                 doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                 if (doubleTap) {
                     InkStrokeProperties.Instance._controlButton = true;
                     InkStrokeProperties.Instance._currentPoint = -1;
-                    this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+                    this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     if (isEditing) {
                         this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }
-            }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)));
-    }
+            }),
+            isEditing,
+            isEditing,
+            action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+        );
+    };
 
     /**
      * @param scrPt a point in the screen coordinate space
      * @returns the point in the ink data's coordinate space.
      */
-    ptFromScreen = (scrPt: { X: number, Y: number }) => {
+    ptFromScreen = (scrPt: { X: number; Y: number }) => {
         const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y);
         const inkPt = {
@@ -179,39 +197,39 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
         };
         return inkPt;
-    }
+    };
 
     /**
      * @param inkPt a point in the ink data's coordinate space
      * @returns the screen point corresponding to the ink point
      */
-    ptToScreen = (inkPt: { X: number, Y: number }) => {
+    ptToScreen = (inkPt: { X: number; Y: number }) => {
         const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const docPt = {
             X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
-            Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2
+            Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2,
         };
         const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y);
         return { X: scrPt[0], Y: scrPt[1] };
-    }
+    };
 
     /**
-    * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
-    * @param scrPt - the point to snap to this stroke
-    * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
-    *
-    * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
-    */
-    snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
+     * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
+     * @param scrPt - the point to snap to this stroke
+     * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
+     *
+     * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
+     */
+    snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => {
         const { inkData } = this.inkScaledData();
         const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
         return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale };
-    }
+    };
 
     /**
-    * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
-    * factor for converting between ink and screen space.
-    */
+     * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
+     * factor for converting between ink and screen space.
+     */
     inkScaledData = () => {
         const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
@@ -228,27 +246,31 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             inkLeft,
             inkWidth,
             inkHeight,
-            inkScaleX: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1),
-            inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1)
+            inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
+            inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
         };
-    }
+    };
 
     //
-    // this updates the highlight for the nearest point on the curve to the cursor.  
+    // this updates the highlight for the nearest point on the curve to the cursor.
     // if the user double clicks, this highlighted point will be added as a control point in the curve.
     //
     @action
     onPointerMove = (e: React.PointerEvent) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
-            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
-            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+        const screenPts = inkData
+            .map(point =>
+                this.screenToLocal()
+                    .inverse()
+                    .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+            )
+            .map(p => ({ X: p[0], Y: p[1] }));
         const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
 
         this._nearestT = nearestT;
         this._nearestSeg = nearestSeg;
         this._nearestScrPt = nearestPt;
-    }
+    };
 
     /**
      * @returns the nearest screen point to the cursor (to render a highlight for the point to be added)
@@ -263,50 +285,66 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.props.Document;
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale);  // the width of the blue line widget that shows the centerline of the ink stroke
+        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
 
         const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
-        const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
-            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
-            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+        const screenPts = inkData
+            .map(point =>
+                this.screenToLocal()
+                    .inverse()
+                    .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+            )
+            .map(p => ({ X: p[0], Y: p[1] }));
         const screenHdlPts = screenPts;
 
         const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         const markerScale = NumCast(this.layoutDoc.strokeMarkerScale);
-        return SnappingManager.GetIsDragging() ? (null) :
-            !InkStrokeProperties.Instance._controlButton ?
-                (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
-                    <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
-                        <InkEndPtHandles
-                            inkView={this.props.docViewPath().lastElement()}
-                            inkDoc={inkDoc}
-                            startPt={screenPts[0]}
-                            endPt={screenPts.lastElement()}
-                            screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
-                    </div>) :
+        return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? (
+            !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
                 <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
-                    {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
-                        StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
-                        "none", startMarker, endMarker, markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth), StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
-                    <InkControlPtHandles
-                        inkView={this.props.docViewPath().lastElement()}
-                        inkDoc={inkDoc}
-                        inkCtrlPoints={inkData}
-                        screenCtrlPoints={screenHdlPts}
-                        nearestScreenPt={this.nearestScreenPt}
-                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
-                    <InkTangentHandles
-                        inkView={this.props.docViewPath().lastElement()}
-                        inkDoc={inkDoc}
-                        screenCtrlPoints={screenHdlPts}
-                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
-                        ScreenToLocalTransform={this.screenToLocal} />
-                </div>;
-    }
+                    <InkEndPtHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+                </div>
+            )
+        ) : (
+            <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+                {InteractionUtils.CreatePolyline(
+                    screenPts,
+                    0,
+                    0,
+                    Colors.MEDIUM_BLUE,
+                    screenInkWidth[0],
+                    screenSpaceCenterlineStrokeWidth,
+                    StrCast(inkDoc.strokeLineJoin),
+                    StrCast(this.layoutDoc.strokeLineCap),
+                    StrCast(inkDoc.strokeBezier),
+                    'none',
+                    startMarker,
+                    endMarker,
+                    markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth),
+                    StrCast(inkDoc.strokeDash),
+                    1,
+                    1,
+                    '',
+                    'none',
+                    1.0,
+                    false
+                )}
+                <InkControlPtHandles
+                    inkView={this.props.docViewPath().lastElement()}
+                    inkDoc={inkDoc}
+                    inkCtrlPoints={inkData}
+                    screenCtrlPoints={screenHdlPts}
+                    nearestScreenPt={this.nearestScreenPt}
+                    screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
+                />
+                <InkTangentHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+            </div>
+        );
+    };
 
     _subContentView: DocComponentView | undefined;
-    setSubContentView = (doc: DocComponentView) => this._subContentView = doc;
+    setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
     render() {
         TraceMobx();
         const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
@@ -315,105 +353,181 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
         const closed = InkingStroke.IsClosed(inkData);
-        const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
-        const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);
+        const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+        const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
 
         // Visually renders the polygonal line made by the user.
-        const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth,
-            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
-            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
-            markerScale, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
+        const inkLine = InteractionUtils.CreatePolyline(
+            inkData,
+            inkLeft,
+            inkTop,
+            strokeColor,
+            inkStrokeWidth,
+            inkStrokeWidth,
+            StrCast(this.layoutDoc.strokeLineJoin),
+            StrCast(this.layoutDoc.strokeLineCap),
+            StrCast(this.layoutDoc.strokeBezier),
+            !closed ? 'none' : fillColor === 'transparent' ? 'none' : fillColor,
+            startMarker,
+            endMarker,
+            markerScale,
+            StrCast(this.layoutDoc.strokeDash),
+            inkScaleX,
+            inkScaleY,
+            '',
+            'none',
+            1.0,
+            false
+        );
         const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = !highlightIndex ?
-            StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
-            ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
+        const highlightColor = !highlightIndex
+            ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent')
+            : ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'yellow', 'magenta', 'cyan', 'orange'][highlightIndex];
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
-            inkStrokeWidth, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? closed ? 0 : (highlightIndex + 2) : 0),
-            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
-            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker,
-            markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? "none" : "visiblepainted"), 0.0,
-            false, downHdlr);
-        const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", ""));
-        // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. 
+        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) =>
+            InteractionUtils.CreatePolyline(
+                inkData,
+                inkLeft,
+                inkTop,
+                highlightColor,
+                inkStrokeWidth,
+                fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0),
+                StrCast(this.layoutDoc.strokeLineJoin),
+                StrCast(this.layoutDoc.strokeLineCap),
+                StrCast(this.layoutDoc.strokeBezier),
+                !closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor,
+                startMarker,
+                endMarker,
+                markerScale,
+                undefined,
+                inkScaleX,
+                inkScaleY,
+                '',
+                this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? 'none' : 'visiblepainted'),
+                0.0,
+                false,
+                downHdlr
+            );
+        const fsize = +StrCast(this.props.Document.fontSize, '12px').replace('px', '');
+        // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
         // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
         // see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
-        const lineHeightGuess = (+getComputedStyle(document.body).lineHeight.replace("px", "")) / (+getComputedStyle(document.body).fontSize.replace("px", ""));
+        const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
         const interactions = {
-            onPointerLeave: action(() => this._nearestScrPt = undefined),
+            onPointerLeave: action(() => (this._nearestScrPt = undefined)),
             onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
             onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
             onContextMenu: () => {
                 const cm = ContextMenu.Instance;
-                !Doc.noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
-                cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
-            }
+                !Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
+                cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.rootDoc), icon: 'paint-brush' });
+                cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+            },
         };
-        return <div className="inkStroke-wrapper">
-            <svg className="inkStroke"
-                style={{
-                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
-                    cursor: this.props.isSelected() ? "default" : undefined
-                }}
-                {...(!closed ? interactions : {})}
-            >
-                {closed ? inkLine : clickableLine(this.onPointerDown)}
-                {closed ? clickableLine(this.onPointerDown) : inkLine}
-            </svg>
-            {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) :
-                <div className="inkStroke-text" style={{
-                    color: StrCast(this.layoutDoc.textColor, "black"),
-                    pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
-                    width: this.layoutDoc[WidthSym](),
-                    transform: `scale(${this.props.scaling?.() || 1})`,
-                    transformOrigin: "top left",
-                    top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.scaling?.() || 1)) / 2
-                }}>
-                    <FormattedTextBox
-                        {...OmitKeys(this.props, ['children']).omit}
-                        setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
-                        yPadding={10}
-                        xPadding={10}
-                        fieldKey={"text"}
-                        fontSize={fsize}
-                        dontRegisterView={true}
-                        noSidebar={true}
-                        dontScale={true}
-                        isContentActive={this.isContentActive}
-                    />
-                </div>
-            }
-            {!closed ? null : <svg className="inkStroke"
-                style={{
-                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
-                    cursor: this.props.isSelected() ? "default" : undefined, position: "absolute"
-                }}
-                {...interactions}
-            >
-                {clickableLine(this.onPointerDown, true)}
-            </svg>}
-        </div>;
+        return (
+            <div className="inkStroke-wrapper">
+                <svg
+                    className="inkStroke"
+                    style={{
+                        transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                        cursor: this.props.isSelected() ? 'default' : undefined,
+                    }}
+                    {...(!closed ? interactions : {})}>
+                    {closed ? inkLine : clickableLine(this.onPointerDown)}
+                    {closed ? clickableLine(this.onPointerDown) : inkLine}
+                </svg>
+                {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? null : (
+                    <div
+                        className="inkStroke-text"
+                        style={{
+                            color: StrCast(this.layoutDoc.textColor, 'black'),
+                            pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
+                            width: this.layoutDoc[WidthSym](),
+                            transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
+                            transformOrigin: 'top left',
+                            top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
+                        }}>
+                        <FormattedTextBox
+                            {...OmitKeys(this.props, ['children']).omit}
+                            setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
+                            yPadding={10}
+                            xPadding={10}
+                            fieldKey={'text'}
+                            fontSize={fsize}
+                            dontRegisterView={true}
+                            noSidebar={true}
+                            dontScale={true}
+                            isContentActive={this.isContentActive}
+                        />
+                    </div>
+                )}
+                {!closed ? null : (
+                    <svg
+                        className="inkStroke"
+                        style={{
+                            transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                            mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                            cursor: this.props.isSelected() ? 'default' : undefined,
+                            position: 'absolute',
+                        }}
+                        {...interactions}>
+                        {clickableLine(this.onPointerDown, true)}
+                    </svg>
+                )}
+            </div>
+        );
     }
 }
 
-
-export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
-export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
-export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
-export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
-export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
-export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
-export function SetActiveArrowScale(value: number) { ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); }
-export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
-export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); }
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); }
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+export function SetActiveInkWidth(width: string): void {
+    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveFillColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
+export function ActiveInkPen(): Doc {
+    return Doc.UserDoc();
+}
+export function ActiveInkColor(): string {
+    return StrCast(ActiveInkPen()?.activeInkColor, 'black');
+}
+export function ActiveFillColor(): string {
+    return StrCast(ActiveInkPen()?.activeFillColor, '');
+}
+export function ActiveArrowStart(): string {
+    return StrCast(ActiveInkPen()?.activeArrowStart, '');
+}
+export function ActiveArrowEnd(): string {
+    return StrCast(ActiveInkPen()?.activeArrowEnd, '');
+}
+export function ActiveArrowScale(): number {
+    return NumCast(ActiveInkPen()?.activeArrowScale, 1);
+}
+export function ActiveDash(): string {
+    return StrCast(ActiveInkPen()?.activeDash, '0');
+}
+export function ActiveInkWidth(): number {
+    return Number(ActiveInkPen()?.activeInkWidth);
+}
+export function ActiveInkBezierApprox(): string {
+    return StrCast(ActiveInkPen()?.activeInkBezier);
+}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index e81a9c40f..9fdf9d2be 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -93,7 +93,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         this.props
             .ScreenToLocalTransform()
             .translate(Doc.NativeWidth(this.props.dataDoc), 0)
-            .scale(this.props.scaling?.() || 1);
+            .scale(this.props.NativeDimScaling?.() || 1);
     // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 :
     //     this.props.usePanelWidth ? this.props.PanelWidth() :
     //         (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
@@ -164,7 +164,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
-                        scaling={returnOne}
+                        NativeDimScaling={returnOne}
                         childShowTitle={this.showTitle}
                         childDocumentsActive={this.props.isContentActive}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 340a5df45..334f381be 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -250,9 +250,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
                         : props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK
-                        ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1 && doc.type !== DocumentType.INK
-                        ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : isBackground()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                         : StrCast(doc.boxShadow, '');
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index f1d842386..714d8421a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -519,7 +519,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     isAnnotationOverlay={true}
                     isDocumentActive={returnFalse}
                     select={emptyFunction}
-                    scaling={returnOne}
+                    NativeDimScaling={returnOne}
                     xMargin={25}
                     yMargin={10}
                     ScreenToLocalTransform={this.dictationScreenToLocalTransform}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 809a73a77..dce792d19 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -235,7 +235,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight
                 PanelWidth={this.documentTitleWidth}
                 PanelHeight={this.documentTitleHeight}
-                scaling={returnOne}
+                NativeDimScaling={returnOne}
                 onKey={this.onKey}
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
@@ -351,7 +351,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return Doc.NativeHeight(this.Document, undefined, true);
     }
 
-    @computed get contentScaling() {
+    /// scale factor for tree view so that it will fit within it's panel bounds
+    @computed get nativeDimScaling() {
         const nw = this.nativeWidth;
         const nh = this.nativeHeight;
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
@@ -365,7 +366,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
-    panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.scaling?.() || 1);
+    panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1);
 
     addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
     remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
@@ -389,9 +390,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 <div
                     className="collectionTreeView-container"
                     style={{
-                        transform: this.outlineMode ? `scale(${this.contentScaling})` : '',
+                        transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
                         paddingLeft: `${this.marginX()}px`,
-                        width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : '',
+                        width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
                     }}
                     onContextMenu={this.onContextMenu}>
                     {!this.buttonMenu && !this.noviceExplainer ? null : (
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 5a2103e98..aa1330762 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -403,7 +403,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const aspect = Doc.NativeAspect(layoutDoc);
         if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]());
         if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth()));
-        return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
+        return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
     };
     docHeight = () => {
         const layoutDoc = this.layoutDoc;
@@ -514,7 +514,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     rtfWidth = () => {
         const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
-        return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1);
+        return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1);
     };
     rtfHeight = () => {
         const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
@@ -921,7 +921,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                 searchFilterDocs={returnEmptyDoclist}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={this.props.treeView.props.Document}
-                ContentScaling={returnOne}
             />
         );
 
@@ -992,7 +991,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideResizeHandles={this.props.treeView.outlineMode}
                 onClick={this.onChildClick}
                 focus={this.refocus}
-                ContentScaling={returnOne}
                 onKey={this.onKeyDown}
                 hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d7a12122..3e938ec1c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -58,7 +58,7 @@ import e = require('connect-flash');
 export type collectionFreeformViewProps = {
     annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
-    childPointerEvents?: boolean;
+    childPointerEvents?: string;
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
@@ -150,11 +150,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
     }
     @computed get cachedCenteringShiftX(): number {
-        const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling;
+        const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
-        const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling;
+        const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedGetLocalTransform(): Transform {
@@ -1065,7 +1065,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
             // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
@@ -1251,7 +1251,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         if (scale) {
             const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
-            const newScale = Math.min(maxZoom, (1 / (this.contentScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+            const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
             return {
                 panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
                 panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
@@ -1301,7 +1301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     pointerEvents = () => {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
-        const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ? 'all' : this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.();
+        const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -1631,9 +1631,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 img.style.width = canvas.style.width;
                 img.style.height = canvas.style.height;
                 const newCan = newDiv as HTMLCanvasElement;
-                const parEle = newCan.parentElement as HTMLElement;
-                parEle.removeChild(newCan);
-                parEle.appendChild(img);
+                if (newCan) {
+                    const parEle = newCan.parentElement as HTMLElement;
+                    parEle.removeChild(newCan);
+                    parEle.appendChild(img);
+                }
             }
         }
     }
@@ -1943,13 +1945,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
 
-    @computed get contentScaling() {
+    @computed get nativeDimScaling() {
         if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
         const nw = this.nativeWidth;
         const nh = this.nativeHeight;
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
-        return wscale < hscale ? wscale : hscale;
+        return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
     }
 
     private groupDropDisposer?: DragManager.DragDropDisposer;
@@ -1983,9 +1985,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
                             ? 'all'
                             : (this.props.pointerEvents?.() as any),
-                    transform: `scale(${this.contentScaling || 1})`,
-                    width: `${100 / (this.contentScaling || 1)}%`,
-                    height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+                    transform: `scale(${this.nativeDimScaling || 1})`,
+                    width: `${100 / (this.nativeDimScaling || 1)}%`,
+                    height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
                 }}>
                 {this._firstRender ? this.placeholder : this.marqueeView}
                 {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 777ef464f..465dbfe6d 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,6 +1,6 @@
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
-import * as React from "react";
+import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -11,11 +11,10 @@ import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
-import "./CollectionMulticolumnView.scss";
+import './CollectionMulticolumnView.scss';
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 
-
 interface WidthSpecifier {
     magnitude: number;
     unit: string;
@@ -27,8 +26,8 @@ interface LayoutData {
 }
 
 export const DimUnit = {
-    Pixel: "px",
-    Ratio: "*"
+    Pixel: 'px',
+    Ratio: '*',
 };
 
 const resolvedUnits = Object.values(DimUnit);
@@ -36,14 +35,13 @@ const resizerWidth = 8;
 
 @observer
 export class CollectionMulticolumnView extends CollectionSubView() {
-
     /**
      * @returns the list of layout documents whose width unit is
      * *, denoting that it will be displayed with a ratio, not fixed pixel, value
      */
     @computed
     private get ratioDefinedDocs() {
-        return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
+        return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
     }
 
     @computed
@@ -65,10 +63,10 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let starSum = 0;
         const widthSpecifiers: WidthSpecifier[] = [];
         this.childLayoutPairs.map(pair => {
-            const unit = StrCast(pair.layout._dimUnit, "*");
+            const unit = StrCast(pair.layout._dimUnit, '*');
             const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
-                (unit === DimUnit.Ratio) && (starSum += magnitude);
+                unit === DimUnit.Ratio && (starSum += magnitude);
                 widthSpecifiers.push({ magnitude, unit });
             }
             /**
@@ -100,14 +98,13 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * This returns the total quantity, in pixels, that this
      * view needs to reserve for child documents that have
      * (with higher priority) requested a fixed pixel width.
-     * 
+     *
      * If the underlying resolvedLayoutInformation returns null
      * because we're waiting on promises to resolve, this value will be undefined as well.
      */
     @computed
     private get totalFixedAllocation(): number | undefined {
-        return this.resolvedLayoutInformation?.widthSpecifiers.reduce(
-            (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
+        return this.resolvedLayoutInformation?.widthSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
     }
 
     /**
@@ -115,7 +112,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * view needs to reserve for child documents that have
      * (with lower priority) requested a certain relative proportion of the
      * remaining pixel width not allocated for fixed widths.
-     * 
+     *
      * If the underlying totalFixedAllocation returns undefined
      * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
      */
@@ -135,7 +132,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * this accessor returns 1000 / (2 + 2 + 1), or 200px.
      * Elsewhere, this is then multiplied by each relative-width
      * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
-     * 
+     *
      * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
      * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
      */
@@ -165,17 +162,17 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             return 0; // we're still waiting on promises to resolve
         }
         let width = NumCast(layout._dimMagnitude, this.minimumDim);
-        if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
+        if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
             width *= columnUnitLength;
         }
         return width;
-    }
+    };
 
     /**
      * @returns the transform that will correctly place
      * the document decorations box, shifted to the right by
      * the sum of all the resolved column widths of the
-     * documents before the target. 
+     * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
         const columnUnitLength = this.columnUnitLength;
@@ -185,12 +182,12 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this.props.ScreenToLocalTransform().translate(-offset / (this.props.scaling?.() || 1), 0);
+                return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0);
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
         }
         return Transform.Identity(); // type coersion, this case should never be hit
-    }
+    };
 
     @undoBatch
     @action
@@ -198,16 +195,17 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
-                curInd = this.childDocs.indexOf(d);
-            }));
+            de.complete.docDragData?.droppedDocuments.forEach(
+                action((d: Doc) => {
+                    curInd = this.childDocs.indexOf(d);
+                })
+            );
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.x < de.x && brect.x + brect.width > de.x) {
                     if (curInd !== -1 && curInd === Math.floor(index / 2)) {
                         dropInd = curInd;
-                    }
-                    else if (child.className === "multiColumnResizer") {
+                    } else if (child.className === 'multiColumnResizer') {
                         dropInd = Math.floor(index / 2);
                     } else {
                         dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1));
@@ -215,76 +213,80 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 }
             });
             if (super.onInternalDrop(e, de)) {
-                de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
-                    d._dimUnit = "*";
-                    d._dimMagnitude = 1;
-                    if (dropInd !== curInd || dropInd === -1) {
-                        if (this.childDocs.includes(d)) {
-                            if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+                de.complete.docDragData?.droppedDocuments.forEach(
+                    action((d: Doc) => {
+                        d._dimUnit = '*';
+                        d._dimMagnitude = 1;
+                        if (dropInd !== curInd || dropInd === -1) {
+                            if (this.childDocs.includes(d)) {
+                                if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+                            }
+                            Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+                            Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
-                        Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
-                        Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
-                    }
-                }));
+                    })
+                );
             }
         }
         return false;
-    }
-
+    };
 
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
     addDocTab = (doc: Doc, where: string) => {
-        if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+        if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
             return true;
         }
         return this.props.addDocTab(doc, where);
-    }
+    };
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
-    isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
+    isChildContentActive = () =>
+        ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
     getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
-        return <DocumentView
-            Document={layout}
-            DataDoc={layout.resolvedDataDoc as Doc}
-            styleProvider={this.props.styleProvider}
-            docViewPath={this.props.docViewPath}
-            LayoutTemplate={this.props.childLayoutTemplate}
-            LayoutTemplateString={this.props.childLayoutString}
-            renderDepth={this.props.renderDepth + 1}
-            PanelWidth={width}
-            PanelHeight={height}
-            rootSelected={this.rootSelected}
-            dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
-            onClick={this.onChildClickHandler}
-            onDoubleClick={this.onChildDoubleClickHandler}
-            suppressSetHeight={true}
-            ScreenToLocalTransform={dxf}
-            isContentActive={this.isChildContentActive}
-            isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-            hideResizeHandles={this.props.childHideResizeHandles?.()}
-            hideDecorationTitle={this.props.childHideDecorationTitle?.()}
-            fitContentsToBox={this.props.fitContentsToBox}
-            focus={this.props.focus}
-            docFilters={this.childDocFilters}
-            docRangeFilters={this.childDocRangeFilters}
-            searchFilterDocs={this.searchFilterDocs}
-            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-            ContainingCollectionView={this.props.CollectionView}
-            dontRegisterView={this.props.dontRegisterView}
-            addDocument={this.props.addDocument}
-            moveDocument={this.props.moveDocument}
-            removeDocument={this.props.removeDocument}
-            whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-            addDocTab={this.addDocTab}
-            pinToPres={this.props.pinToPres}
-            bringToFront={returnFalse}
-        />;
-    }
+        return (
+            <DocumentView
+                Document={layout}
+                DataDoc={layout.resolvedDataDoc as Doc}
+                styleProvider={this.props.styleProvider}
+                docViewPath={this.props.docViewPath}
+                LayoutTemplate={this.props.childLayoutTemplate}
+                LayoutTemplateString={this.props.childLayoutString}
+                renderDepth={this.props.renderDepth + 1}
+                PanelWidth={width}
+                PanelHeight={height}
+                rootSelected={this.rootSelected}
+                dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+                onClick={this.onChildClickHandler}
+                onDoubleClick={this.onChildDoubleClickHandler}
+                suppressSetHeight={true}
+                ScreenToLocalTransform={dxf}
+                isContentActive={this.isChildContentActive}
+                isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+                hideResizeHandles={this.props.childHideResizeHandles?.()}
+                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+                fitContentsToBox={this.props.fitContentsToBox}
+                focus={this.props.focus}
+                docFilters={this.childDocFilters}
+                docRangeFilters={this.childDocRangeFilters}
+                searchFilterDocs={this.searchFilterDocs}
+                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+                ContainingCollectionView={this.props.CollectionView}
+                dontRegisterView={this.props.dontRegisterView}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                addDocTab={this.addDocTab}
+                pinToPres={this.props.pinToPres}
+                bringToFront={returnFalse}
+            />
+        );
+    };
     /**
      * @returns the resolved list of rendered child documents, displayed
-     * at their resolved pixel widths, each separated by a resizer. 
+     * at their resolved pixel widths, each separated by a resizer.
      */
     @computed
     private get contents(): JSX.Element[] | null {
@@ -293,22 +295,20 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
-            const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1));
+            const dxf = () =>
+                this.lookupIndividualTransform(layout)
+                    .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+                    .scale(this.props.NativeDimScaling?.() || 1);
             const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
-                <div className={"document-wrapper"}
-                    key={"wrapper" + i}
-                    style={{ width: width() }} >
+                <div className={'document-wrapper'} key={'wrapper' + i} style={{ width: width() }}>
                     {this.getDisplayDoc(layout, dxf, width, height)}
-                    <WidthLabel
-                        layout={layout}
-                        collectionDoc={Document}
-                    />
+                    <WidthLabel layout={layout} collectionDoc={Document} />
                 </div>,
                 <ResizeBar
                     width={resizerWidth}
-                    key={"resizer" + i}
+                    key={'resizer' + i}
                     styleProvider={this.props.styleProvider}
                     isContentActive={this.props.isContentActive}
                     select={this.props.select}
@@ -324,16 +324,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
 
     render(): JSX.Element {
         return (
-            <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget}
+            <div
+                className={'collectionMulticolumnView_contents'}
+                ref={this.createDashEventsTarget}
                 style={{
                     width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
                     height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
-                    marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
-                    marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
-                }} >
+                    marginLeft: NumCast(this.props.Document._xMargin),
+                    marginRight: NumCast(this.props.Document._xMargin),
+                    marginTop: NumCast(this.props.Document._yMargin),
+                    marginBottom: NumCast(this.props.Document._yMargin),
+                }}>
                 {this.contents}
             </div>
         );
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 08385bcb5..f8de4e5de 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,6 +1,6 @@
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
-import * as React from "react";
+import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -11,7 +11,7 @@ import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
-import "./CollectionMultirowView.scss";
+import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
 
@@ -26,8 +26,8 @@ interface LayoutData {
 }
 
 export const DimUnit = {
-    Pixel: "px",
-    Ratio: "*"
+    Pixel: 'px',
+    Ratio: '*',
 };
 
 const resolvedUnits = Object.values(DimUnit);
@@ -35,14 +35,13 @@ const resizerHeight = 8;
 
 @observer
 export class CollectionMultirowView extends CollectionSubView() {
-
     /**
      * @returns the list of layout documents whose width unit is
      * *, denoting that it will be displayed with a ratio, not fixed pixel, value
      */
     @computed
     private get ratioDefinedDocs() {
-        return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
+        return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
     }
 
     @computed
@@ -64,10 +63,10 @@ export class CollectionMultirowView extends CollectionSubView() {
         let starSum = 0;
         const heightSpecifiers: HeightSpecifier[] = [];
         this.childLayoutPairs.map(pair => {
-            const unit = StrCast(pair.layout._dimUnit, "*");
+            const unit = StrCast(pair.layout._dimUnit, '*');
             const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
-                (unit === DimUnit.Ratio) && (starSum += magnitude);
+                unit === DimUnit.Ratio && (starSum += magnitude);
                 heightSpecifiers.push({ magnitude, unit });
             }
             /**
@@ -99,14 +98,13 @@ export class CollectionMultirowView extends CollectionSubView() {
      * This returns the total quantity, in pixels, that this
      * view needs to reserve for child documents that have
      * (with higher priority) requested a fixed pixel width.
-     * 
+     *
      * If the underlying resolvedLayoutInformation returns null
      * because we're waiting on promises to resolve, this value will be undefined as well.
      */
     @computed
     private get totalFixedAllocation(): number | undefined {
-        return this.resolvedLayoutInformation?.heightSpecifiers.reduce(
-            (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
+        return this.resolvedLayoutInformation?.heightSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
     }
 
     /**
@@ -114,7 +112,7 @@ export class CollectionMultirowView extends CollectionSubView() {
      * view needs to reserve for child documents that have
      * (with lower priority) requested a certain relative proportion of the
      * remaining pixel width not allocated for fixed widths.
-     * 
+     *
      * If the underlying totalFixedAllocation returns undefined
      * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
      */
@@ -134,7 +132,7 @@ export class CollectionMultirowView extends CollectionSubView() {
      * this accessor returns 1000 / (2 + 2 + 1), or 200px.
      * Elsewhere, this is then multiplied by each relative-width
      * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
-     * 
+     *
      * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
      * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
      */
@@ -164,17 +162,17 @@ export class CollectionMultirowView extends CollectionSubView() {
             return 0; // we're still waiting on promises to resolve
         }
         let height = NumCast(layout._dimMagnitude, this.minimumDim);
-        if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
+        if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
             height *= rowUnitLength;
         }
         return height;
-    }
+    };
 
     /**
      * @returns the transform that will correctly place
      * the document decorations box, shifted to the right by
      * the sum of all the resolved row widths of the
-     * documents before the target. 
+     * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
         const rowUnitLength = this.rowUnitLength;
@@ -184,13 +182,12 @@ export class CollectionMultirowView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.scaling?.() || 1));
+                return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerHeight;
         }
         return Transform.Identity(); // type coersion, this case should never be hit
-    }
-
+    };
 
     @undoBatch
     @action
@@ -198,16 +195,17 @@ export class CollectionMultirowView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
-                curInd = this.childDocs.indexOf(d);
-            }));
+            de.complete.docDragData?.droppedDocuments.forEach(
+                action((d: Doc) => {
+                    curInd = this.childDocs.indexOf(d);
+                })
+            );
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.y < de.y && brect.y + brect.height > de.y) {
                     if (curInd !== -1 && curInd === Math.floor(index / 2)) {
                         dropInd = curInd;
-                    }
-                    else if (child.className === "multiColumnResizer") {
+                    } else if (child.className === 'multiColumnResizer') {
                         dropInd = Math.floor(index / 2);
                     } else {
                         dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1));
@@ -215,75 +213,79 @@ export class CollectionMultirowView extends CollectionSubView() {
                 }
             });
             if (super.onInternalDrop(e, de)) {
-                de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
-                    d._dimUnit = "*";
-                    d._dimMagnitude = 1;
-                    if (dropInd !== curInd || dropInd === -1) {
-                        if (this.childDocs.includes(d)) {
-                            if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+                de.complete.docDragData?.droppedDocuments.forEach(
+                    action((d: Doc) => {
+                        d._dimUnit = '*';
+                        d._dimMagnitude = 1;
+                        if (dropInd !== curInd || dropInd === -1) {
+                            if (this.childDocs.includes(d)) {
+                                if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+                            }
+                            Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+                            Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
-                        Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
-                        Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
-                    }
-                }));
+                    })
+                );
             }
         }
         return false;
-    }
-
+    };
 
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
     addDocTab = (doc: Doc, where: string) => {
-        if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+        if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
             return true;
         }
         return this.props.addDocTab(doc, where);
-    }
+    };
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
-    isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
+    isChildContentActive = () =>
+        ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
     getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
-        return <DocumentView
-            Document={layout}
-            DataDoc={layout.resolvedDataDoc as Doc}
-            styleProvider={this.props.styleProvider}
-            docViewPath={this.props.docViewPath}
-            LayoutTemplate={this.props.childLayoutTemplate}
-            LayoutTemplateString={this.props.childLayoutString}
-            renderDepth={this.props.renderDepth + 1}
-            PanelWidth={width}
-            PanelHeight={height}
-            rootSelected={this.rootSelected}
-            dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
-            onClick={this.onChildClickHandler}
-            onDoubleClick={this.onChildDoubleClickHandler}
-            ScreenToLocalTransform={dxf}
-            isContentActive={this.isChildContentActive}
-            isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-            hideResizeHandles={this.props.childHideResizeHandles?.()}
-            hideDecorationTitle={this.props.childHideDecorationTitle?.()}
-            fitContentsToBox={this.props.fitContentsToBox}
-            focus={this.props.focus}
-            docFilters={this.childDocFilters}
-            docRangeFilters={this.childDocRangeFilters}
-            searchFilterDocs={this.searchFilterDocs}
-            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-            ContainingCollectionView={this.props.CollectionView}
-            dontRegisterView={this.props.dontRegisterView}
-            addDocument={this.props.addDocument}
-            moveDocument={this.props.moveDocument}
-            removeDocument={this.props.removeDocument}
-            whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-            addDocTab={this.addDocTab}
-            pinToPres={this.props.pinToPres}
-            bringToFront={returnFalse}
-        />;
-    }
+        return (
+            <DocumentView
+                Document={layout}
+                DataDoc={layout.resolvedDataDoc as Doc}
+                styleProvider={this.props.styleProvider}
+                docViewPath={this.props.docViewPath}
+                LayoutTemplate={this.props.childLayoutTemplate}
+                LayoutTemplateString={this.props.childLayoutString}
+                renderDepth={this.props.renderDepth + 1}
+                PanelWidth={width}
+                PanelHeight={height}
+                rootSelected={this.rootSelected}
+                dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+                onClick={this.onChildClickHandler}
+                onDoubleClick={this.onChildDoubleClickHandler}
+                ScreenToLocalTransform={dxf}
+                isContentActive={this.isChildContentActive}
+                isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+                hideResizeHandles={this.props.childHideResizeHandles?.()}
+                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+                fitContentsToBox={this.props.fitContentsToBox}
+                focus={this.props.focus}
+                docFilters={this.childDocFilters}
+                docRangeFilters={this.childDocRangeFilters}
+                searchFilterDocs={this.searchFilterDocs}
+                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+                ContainingCollectionView={this.props.CollectionView}
+                dontRegisterView={this.props.dontRegisterView}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                addDocTab={this.addDocTab}
+                pinToPres={this.props.pinToPres}
+                bringToFront={returnFalse}
+            />
+        );
+    };
     /**
      * @returns the resolved list of rendered child documents, displayed
-     * at their resolved pixel widths, each separated by a resizer. 
+     * at their resolved pixel widths, each separated by a resizer.
      */
     @computed
     private get contents(): JSX.Element[] | null {
@@ -292,13 +294,14 @@ export class CollectionMultirowView extends CollectionSubView() {
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
-            const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1));
+            const dxf = () =>
+                this.lookupIndividualTransform(layout)
+                    .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+                    .scale(this.props.NativeDimScaling?.() || 1);
             const height = () => this.lookupPixels(layout);
             const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
-                <div className={"document-wrapper"}
-                    style={{ height: height() }}
-                    key={"wrapper" + i} >
+                <div className={'document-wrapper'} style={{ height: height() }} key={'wrapper' + i}>
                     {this.getDisplayDoc(layout, dxf, width, height)}
                     <HeightLabel layout={layout} collectionDoc={Document} />
                 </div>,
@@ -306,7 +309,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                     height={resizerHeight}
                     styleProvider={this.props.styleProvider}
                     isContentActive={this.props.isContentActive}
-                    key={"resizer" + i}
+                    key={'resizer' + i}
                     columnUnitLength={this.getRowUnitLength}
                     toTop={layout}
                     toBottom={childLayoutPairs[i + 1]?.layout}
@@ -319,16 +322,19 @@ export class CollectionMultirowView extends CollectionSubView() {
 
     render(): JSX.Element {
         return (
-            <div className={"collectionMultirowView_contents"}
+            <div
+                className={'collectionMultirowView_contents'}
                 style={{
                     width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
                     height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
-                    marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
-                    marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
-                }} ref={this.createDashEventsTarget}>
+                    marginLeft: NumCast(this.props.Document._xMargin),
+                    marginRight: NumCast(this.props.Document._xMargin),
+                    marginTop: NumCast(this.props.Document._yMargin),
+                    marginBottom: NumCast(this.props.Document._yMargin),
+                }}
+                ref={this.createDashEventsTarget}>
                 {this.contents}
             </div>
         );
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f1d8123da..381436a56 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -118,7 +118,7 @@ export class DocumentContentsView extends React.Component<
         FormattedTextBoxProps & {
             isSelected: (outsideReaction: boolean) => boolean;
             select: (ctrl: boolean) => void;
-            scaling?: () => number;
+            NativeDimScaling?: () => number;
             setHeight?: (height: number) => void;
             layoutKey: string;
         }
@@ -161,7 +161,6 @@ export class DocumentContentsView extends React.Component<
             'LayoutTemplateString',
             'LayoutTemplate',
             'dontCenter',
-            'ContentScaling',
             'contextMenuItems',
             'onClick',
             'onDoubleClick',
@@ -195,7 +194,7 @@ export class DocumentContentsView extends React.Component<
 
         // replace HTML<tag> with corresponding HTML tag as in:  <HTMLdiv> becomes  <HTMLtag Document={props.Document} htmltag='div'>
         const replacer2 = (match: any, p1: string, offset: any, string: any) => {
-            return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.scaling?.() || 1}' htmltag='${p1}'`;
+            return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
         };
         layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 78d35ab99..5939d1680 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,24 +1,24 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../fields/Doc";
-import { StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { Hypothesis } from "../../util/HypothesisUtils";
-import { LinkManager } from "../../util/LinkManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { Colors } from "../global/globalEnums";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { Colors } from '../global/globalEnums';
 import './DocumentLinksButton.scss';
-import { DocumentView } from "./DocumentView";
-import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { TaskCompletionBox } from "./TaskCompletedBox";
-import React = require("react");
+import { DocumentView } from './DocumentView';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { TaskCompletionBox } from './TaskCompletedBox';
+import React = require('react');
 
-const higflyout = require("@hig/flyout");
+const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
@@ -28,7 +28,7 @@ interface DocumentLinksButtonProps {
     AlwaysOn?: boolean;
     InMenu?: boolean;
     StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
-    ContentScaling?: () => number;
+    scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
 }
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -42,53 +42,71 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static invisibleWebDoc: Opt<Doc>;
     public static invisibleWebRef = React.createRef<HTMLDivElement>();
 
-    @action @undoBatch
+    @action
+    @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
             if (this._linkButton.current !== null) {
-                const linkDrag = UndoManager.StartBatch("Drag Link");
-                this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
-                    dragComplete: dropEv => {
-                        if (this.props.View && dropEv.linkDocument) {// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
-                            !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = "hyperlink");
-                        }
-                        linkDrag?.end();
-                    },
-                    hideSource: false
-                });
+                const linkDrag = UndoManager.StartBatch('Drag Link');
+                this.props.View &&
+                    DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
+                        dragComplete: dropEv => {
+                            if (this.props.View && dropEv.linkDocument) {
+                                // dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                                !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = 'hyperlink');
+                            }
+                            linkDrag?.end();
+                        },
+                        hideSource: false,
+                    });
                 return true;
             }
             return false;
         }
         return false;
-    }
+    };
 
     onLinkMenuOpen = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap) {
-                DocumentView.showBackLinks(this.props.View.rootDoc);
-            }
-        }), undefined, undefined,
-            action(() => DocumentLinksButton.LinkEditorDocView = this.props.View));
-    }
+        setupMoveUpEvents(
+            this,
+            e,
+            this.onLinkButtonMoved,
+            emptyFunction,
+            action((e, doubleTap) => {
+                if (doubleTap) {
+                    DocumentView.showBackLinks(this.props.View.rootDoc);
+                }
+            }),
+            undefined,
+            undefined,
+            action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
+        );
+    };
 
     @undoBatch
     onLinkButtonDown = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap && this.props.InMenu && this.props.StartLink) {
-                //action(() => Doc.BrushDoc(this.props.View.Document));
-                if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
-                    DocumentLinksButton.StartLink = undefined;
-                    DocumentLinksButton.StartLinkView = undefined;
-                } else {
-                    DocumentLinksButton.StartLink = this.props.View.props.Document;
-                    DocumentLinksButton.StartLinkView = this.props.View;
+        setupMoveUpEvents(
+            this,
+            e,
+            this.onLinkButtonMoved,
+            emptyFunction,
+            action((e, doubleTap) => {
+                if (doubleTap && this.props.InMenu && this.props.StartLink) {
+                    //action(() => Doc.BrushDoc(this.props.View.Document));
+                    if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+                        DocumentLinksButton.StartLink = undefined;
+                        DocumentLinksButton.StartLinkView = undefined;
+                    } else {
+                        DocumentLinksButton.StartLink = this.props.View.props.Document;
+                        DocumentLinksButton.StartLinkView = this.props.View;
+                    }
                 }
-            }
-        }));
-    }
+            })
+        );
+    };
 
-    @action @undoBatch
+    @action
+    @undoBatch
     onLinkClick = (e: React.MouseEvent): void => {
         if (this.props.InMenu && this.props.StartLink) {
             DocumentLinksButton.AnnotationId = undefined;
@@ -96,108 +114,125 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
-            } else { //if this LinkButton's Document is undefined
+            } else {
+                //if this LinkButton's Document is undefined
                 DocumentLinksButton.StartLink = this.props.View.props.Document;
                 DocumentLinksButton.StartLinkView = this.props.View;
             }
             //action(() => Doc.BrushDoc(this.props.View.Document));
         }
-    }
-
+    };
 
     completeLink = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
-            if (doubleTap && !this.props.StartLink) {
-                if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
-                    DocumentLinksButton.StartLink = undefined;
-                    DocumentLinksButton.StartLinkView = undefined;
-                    DocumentLinksButton.AnnotationId = undefined;
-                } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
-                    const sourceDoc = DocumentLinksButton.StartLink;
-                    const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
-                    const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking?
+        setupMoveUpEvents(
+            this,
+            e,
+            returnFalse,
+            emptyFunction,
+            undoBatch(
+                action((e, doubleTap) => {
+                    if (doubleTap && !this.props.StartLink) {
+                        if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+                            DocumentLinksButton.StartLink = undefined;
+                            DocumentLinksButton.StartLinkView = undefined;
+                            DocumentLinksButton.AnnotationId = undefined;
+                        } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
+                            const sourceDoc = DocumentLinksButton.StartLink;
+                            const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
+                            const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, 'links'); //why is long drag here when this is used for completing links by clicking?
 
-                    LinkManager.currentLink = linkDoc;
+                            LinkManager.currentLink = linkDoc;
 
-                    runInAction(() => {
-                        if (linkDoc) {
-                            TaskCompletionBox.textDisplayed = "Link Created";
-                            TaskCompletionBox.popupX = e.screenX;
-                            TaskCompletionBox.popupY = e.screenY - 133;
-                            TaskCompletionBox.taskCompleted = true;
+                            runInAction(() => {
+                                if (linkDoc) {
+                                    TaskCompletionBox.textDisplayed = 'Link Created';
+                                    TaskCompletionBox.popupX = e.screenX;
+                                    TaskCompletionBox.popupY = e.screenY - 133;
+                                    TaskCompletionBox.taskCompleted = true;
 
-                            LinkDescriptionPopup.popupX = e.screenX;
-                            LinkDescriptionPopup.popupY = e.screenY - 100;
-                            LinkDescriptionPopup.descriptionPopup = true;
+                                    LinkDescriptionPopup.popupX = e.screenX;
+                                    LinkDescriptionPopup.popupY = e.screenY - 100;
+                                    LinkDescriptionPopup.descriptionPopup = true;
 
-                            const rect = document.body.getBoundingClientRect();
-                            if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                                LinkDescriptionPopup.popupX -= 190;
-                                TaskCompletionBox.popupX -= 40;
-                            }
-                            if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                                LinkDescriptionPopup.popupY -= 40;
-                                TaskCompletionBox.popupY -= 40;
-                            }
+                                    const rect = document.body.getBoundingClientRect();
+                                    if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+                                        LinkDescriptionPopup.popupX -= 190;
+                                        TaskCompletionBox.popupX -= 40;
+                                    }
+                                    if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+                                        LinkDescriptionPopup.popupY -= 40;
+                                        TaskCompletionBox.popupY -= 40;
+                                    }
 
-                            setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
+                                    setTimeout(
+                                        action(() => (TaskCompletionBox.taskCompleted = false)),
+                                        2500
+                                    );
+                                }
+                            });
                         }
-                    });
-                }
-            }
-        })));
-    }
+                    }
+                })
+            )
+        );
+    };
 
-    public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => {
-        if (startLink === endLink) {
-            DocumentLinksButton.StartLink = undefined;
-            DocumentLinksButton.StartLinkView = undefined;
-            DocumentLinksButton.AnnotationId = undefined;
-            DocumentLinksButton.AnnotationUri = undefined;
-            //!this.props.StartLink 
-        } else if (startLink !== endLink) {
-            endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
-            startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
-            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink },
-                DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : undefined, undefined, undefined, true);
+    public static finishLinkClick = undoBatch(
+        action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView) => {
+            if (startLink === endLink) {
+                DocumentLinksButton.StartLink = undefined;
+                DocumentLinksButton.StartLinkView = undefined;
+                DocumentLinksButton.AnnotationId = undefined;
+                DocumentLinksButton.AnnotationUri = undefined;
+                //!this.props.StartLink
+            } else if (startLink !== endLink) {
+                endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
+                startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
+                const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true);
 
-            LinkManager.currentLink = linkDoc;
+                LinkManager.currentLink = linkDoc;
 
-            if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
-                Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
-                Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
-                Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId,
-                    (startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc
-            }
+                if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+                    // if linking from a Hypothes.is annotation
+                    Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
+                    Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
+                    Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
+                    const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
+                    Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
+                }
 
-            if (linkDoc) {
-                TaskCompletionBox.textDisplayed = "Link Created";
-                TaskCompletionBox.popupX = screenX;
-                TaskCompletionBox.popupY = screenY - 133;
-                TaskCompletionBox.taskCompleted = true;
+                if (linkDoc) {
+                    TaskCompletionBox.textDisplayed = 'Link Created';
+                    TaskCompletionBox.popupX = screenX;
+                    TaskCompletionBox.popupY = screenY - 133;
+                    TaskCompletionBox.taskCompleted = true;
 
-                if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
-                    LinkDescriptionPopup.popupX = screenX;
-                    LinkDescriptionPopup.popupY = screenY - 100;
-                    LinkDescriptionPopup.descriptionPopup = true;
-                }
+                    if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) {
+                        LinkDescriptionPopup.popupX = screenX;
+                        LinkDescriptionPopup.popupY = screenY - 100;
+                        LinkDescriptionPopup.descriptionPopup = true;
+                    }
 
-                const rect = document.body.getBoundingClientRect();
-                if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                    LinkDescriptionPopup.popupX -= 190;
-                    TaskCompletionBox.popupX -= 40;
-                }
-                if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                    LinkDescriptionPopup.popupY -= 40;
-                    TaskCompletionBox.popupY -= 40;
-                }
+                    const rect = document.body.getBoundingClientRect();
+                    if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+                        LinkDescriptionPopup.popupX -= 190;
+                        TaskCompletionBox.popupX -= 40;
+                    }
+                    if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+                        LinkDescriptionPopup.popupY -= 40;
+                        TaskCompletionBox.popupY -= 40;
+                    }
 
-                setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
+                    setTimeout(
+                        action(() => {
+                            TaskCompletionBox.taskCompleted = false;
+                        }),
+                        2500
+                    );
+                }
             }
-        }
-    }));
+        })
+    );
 
     @action clearLinks() {
         DocumentLinksButton.StartLink = undefined;
@@ -208,9 +243,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         const results = [] as Doc[];
         const filters = this.props.View.props.docFilters();
         Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
-            if (DocUtils.FilterDocs([link], filters, []).length ||
-                DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length ||
-                DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) {
+            if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length || DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) {
                 results.push(link);
             }
         });
@@ -219,48 +252,45 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     /**
      * gets the JSX of the link button (btn used to start/complete links) OR the link-view button (btn on bottom left of each linked node)
-     * 
+     *
      * todo:glr / anh seperate functionality such as onClick onPointerDown of link menu button
      */
     @computed get linkButtonInner() {
-        const btnDim = "30px";
-        const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />;
-        const isActive = (DocumentLinksButton.StartLink === this.props.View.props.Document) && this.props.StartLink;
-        return (!this.props.InMenu ?
-            <div className="documentLinksButton-cont"
-                style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
-            >
-                <div className={"documentLinksButton"}
-                    onPointerDown={this.onLinkMenuOpen} onClick={this.onLinkClick}
+        const btnDim = '30px';
+        const link = <img style={{ width: '22px', height: '16px' }} src={`/assets/${'link.png'}`} />;
+        const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink;
+        return !this.props.InMenu ? (
+            <div className="documentLinksButton-cont" style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}>
+                <div
+                    className={'documentLinksButton'}
+                    onPointerDown={this.onLinkMenuOpen}
+                    onClick={this.onLinkClick}
                     style={{
                         backgroundColor: Colors.LIGHT_BLUE,
                         color: Colors.BLACK,
-                        fontSize: "20px",
+                        fontSize: '20px',
                         width: btnDim,
                         height: btnDim,
                     }}>
                     {Array.from(this.filteredLinks).length}
                 </div>
             </div>
-            :
-            <div className="documentLinksButton-menu" >
-                {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
-                    <div className={"documentLinksButton-endLink"} ref={this._linkButton}
+        ) : (
+            <div className="documentLinksButton-menu">
+                {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node
+                    <div
+                        className={'documentLinksButton-endLink'}
+                        ref={this._linkButton}
                         onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
                         onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                     </div>
-                    : (null)
-                }
-                {
-                    this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
-                        <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton}
-                            onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
-                            <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
-                        </div>
-                        :
-                        (null)
-                }
+                ) : null}
+                {this.props.InMenu && this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+                    <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
+                        <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+                    </div>
+                ) : null}
             </div>
         );
     }
@@ -268,25 +298,23 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     render() {
         TraceMobx();
 
-        const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
-        const buttonTitle = "Tap to view links; double tap to open link collection";
+        const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+        const buttonTitle = 'Tap to view links; double tap to open link collection';
         const title = this.props.InMenu ? menuTitle : buttonTitle;
 
         //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
-        return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) :
-            <div className="documentLinksButton-wrapper"
+        return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : (
+            <div
+                className="documentLinksButton-wrapper"
                 style={{
-                    transform: this.props.InMenu ? undefined :
-                        `scale(${(this.props.ContentScaling?.() || 1) * this.props.View.screenToLocalTransform().Scale})`
-                }} >
-                {
-                    (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) ||
-                        (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ?
-                        <Tooltip title={<div className="dash-tooltip">{title}</div>}>
-                            {this.linkButtonInner}
-                        </Tooltip>
-                        : this.linkButtonInner
-                }
-            </div>;
+                    transform: this.props.InMenu ? undefined : `scale(${this.props.scaling})`,
+                }}>
+                {(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? (
+                    <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>
+                ) : (
+                    this.linkButtonInner
+                )}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3a8552325..6bf0c365a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -175,7 +175,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
     dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
-    ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     LayoutTemplate?: () => Opt<Doc>;
@@ -192,6 +191,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
 export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeWidth: () => number;
     NativeHeight: () => number;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (ctrlPressed: boolean) => void;
     DocumentView: () => DocumentView;
@@ -235,8 +235,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get ShowTitle() {
         return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
     }
-    @computed get ContentScale() {
-        return this.props.ContentScaling?.() || 1;
+    @computed get NativeDimScaling() {
+        return this.props.NativeDimScaling?.() || 1;
     }
     @computed get thumb() {
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url.href.replace('.png', '_m.png');
@@ -428,7 +428,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 let nheight = Doc.NativeHeight(layoutDoc);
                 const width = layoutDoc._width || 0;
                 const height = layoutDoc._height || (nheight / nwidth) * width;
-                const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
+                const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling;
                 const actualdW = Math.max(width + dW * scale, 20);
                 const actualdH = Math.max(height + dH * scale, 20);
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
@@ -491,10 +491,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
-            const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
+            const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
             dragData.offset = this.props
                 .ScreenToLocalTransform()
-                .scale(this.ContentScale)
+                .scale(this.NativeDimScaling)
                 .transformDirection(x - left, y - top);
             dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);
             dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
@@ -961,7 +961,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: () => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -989,6 +988,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             ? true
             : false;
     };
+    linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
     @computed get contents() {
         TraceMobx();
         const audioView = !this.layoutDoc._showAudio ? null : (
@@ -1041,7 +1041,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     thumbShown={this.thumbShown}
                     isHovering={this.isHovering}
                     setContentView={this.setContentView}
-                    scaling={this.contentScaling}
+                    NativeDimScaling={this.props.NativeDimScaling}
                     PanelHeight={this.panelHeight}
                     setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
                     isContentActive={this.isContentActive}
@@ -1055,7 +1055,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : (
                     <DocumentLinksButton
                         View={this.props.DocumentView()}
-                        ContentScaling={this.props.ContentScaling}
+                        scaling={this.linkButtonInverseScaling}
                         Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -30]}
                     />
                 )}
@@ -1503,7 +1503,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
     }
 
-    toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
+    toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options);
     getBounds = () => {
         if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
@@ -1564,7 +1564,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
-    ContentScale = () => this.nativeScaling;
+    NativeDimScaling = () => this.nativeScaling;
     selfView = () => this;
     screenToLocalTransform = () =>
         this.props
@@ -1620,9 +1620,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
                             NativeHeight={this.NativeHeight}
+                            NativeDimScaling={this.NativeDimScaling}
                             isSelected={this.isSelected}
                             select={this.select}
-                            ContentScaling={this.ContentScale}
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             focus={this.props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index c170f9867..a714518cc 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -12,11 +12,12 @@ import { LightboxView } from '../LightboxView';
 import './EquationBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 
-
 @observer
 export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); }
-    public static SelectOnLoad: string = "";
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(EquationBox, fieldKey);
+    }
+    public static SelectOnLoad: string = '';
     _ref: React.RefObject<EquationEditor> = React.createRef();
     componentDidMount() {
         if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
@@ -25,75 +26,82 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this._ref.current!.mathField.focus();
             this._ref.current!.mathField.select();
         }
-        reaction(() => StrCast(this.dataDoc.text),
+        reaction(
+            () => StrCast(this.dataDoc.text),
             text => {
                 if (text && text !== this._ref.current!.mathField.latex()) {
                     this._ref.current!.mathField.latex(text);
                 }
-            });
-        reaction(() => this.props.isSelected(),
+            }
+        );
+        reaction(
+            () => this.props.isSelected(),
             selected => {
                 if (this._ref.current) {
-                    if (selected) this._ref.current.element.current.children[0].addEventListener("keydown", this.keyPressed, true);
-                    else this._ref.current.element.current.children[0].removeEventListener("keydown", this.keyPressed);
+                    if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true);
+                    else this._ref.current.element.current.children[0].removeEventListener('keydown', this.keyPressed);
                 }
-            }, { fireImmediately: true });
+            },
+            { fireImmediately: true }
+        );
     }
     plot: any;
     @action
     keyPressed = (e: KeyboardEvent) => {
-        const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", ""));
-        const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace("px", ""));
-        if (e.key === "Enter") {
+        const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', ''));
+        const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', ''));
+        if (e.key === 'Enter') {
             const nextEq = Docs.Create.EquationDocument({
-                title: "# math", text: StrCast(this.dataDoc.text), _width, _height: 25,
-                x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10
+                title: '# math',
+                text: StrCast(this.dataDoc.text),
+                _width,
+                _height: 25,
+                x: NumCast(this.layoutDoc.x),
+                y: NumCast(this.layoutDoc.y) + _height + 10,
             });
             EquationBox.SelectOnLoad = nextEq[Id];
             this.props.addDocument?.(nextEq);
             e.stopPropagation();
-
         }
-        if (e.key === "Tab") {
+        if (e.key === 'Tab') {
             const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
                 x: NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym](),
                 y: NumCast(this.layoutDoc.y),
-                _width: 400, _height: 300, backgroundColor: "white"
+                _width: 400,
+                _height: 300,
+                backgroundColor: 'white',
             });
             this.props.addDocument?.(graph);
             e.stopPropagation();
         }
-        if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
-    }
+        if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
+    };
     onChange = (str: string) => {
         this.dataDoc.text = str;
         const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
         if (style) {
-            const _height = Number(style.height.replace("px", ""));
-            const _width = Number(style.width.replace("px", ""));
+            const _height = Number(style.height.replace('px', ''));
+            const _width = Number(style.width.replace('px', ''));
             this.layoutDoc._width = Math.max(35, _width);
             this.layoutDoc._height = Math.max(25, _height);
         }
-    }
+    };
     render() {
         TraceMobx();
-        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
-        return (<div className="equationBox-cont"
-            onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
-            style={{
-                transform: `scale(${scale})`,
-                width: `${100 / scale}%`,
-                height: `${100 / scale}%`,
-                pointerEvents: !this.props.isSelected() ? "none" : undefined,
-            }}
-            onKeyDown={e => e.stopPropagation()}
-        >
-            <EquationEditor ref={this._ref}
-                value={this.dataDoc.text || "x"}
-                spaceBehavesLikeTab={true}
-                onChange={this.onChange}
-                autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
-                autoOperatorNames="sin cos tan" />
-        </div>);
+        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        return (
+            <div
+                className="equationBox-cont"
+                onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
+                style={{
+                    transform: `scale(${scale})`,
+                    width: `${100 / scale}%`,
+                    height: `${100 / scale}%`,
+                    pointerEvents: !this.props.isSelected() ? 'none' : undefined,
+                }}
+                onKeyDown={e => e.stopPropagation()}>
+                <EquationEditor ref={this._ref} value={this.dataDoc.text || 'x'} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+            </div>
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 67cf18d8b..5a6c49809 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,18 +1,18 @@
-import React = require("react");
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { WebField } from "../../../fields/URLField";
-import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
-import { ScriptField } from "../../../fields/ScriptField";
-import { RecordingBox } from "./RecordingBox";
+import React = require('react');
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { DateField } from '../../../fields/DateField';
+import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { WebField } from '../../../fields/URLField';
+import { DocumentView, DocumentViewSharedProps } from './DocumentView';
+import { ScriptField } from '../../../fields/ScriptField';
+import { RecordingBox } from './RecordingBox';
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
 // However, that only happens because the properties are "defined" in the markup for the field view.
-// See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
+// See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc.
 //
 export interface FieldViewProps extends DocumentViewSharedProps {
     // FieldView specific props that are not part of DocumentView props
@@ -23,10 +23,10 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     isContentActive: (outsideReaction?: boolean) => boolean | undefined;
     isDocumentActive?: () => boolean;
     isSelected: (outsideReaction?: boolean) => boolean;
-    scaling?: () => number;
     setHeight?: (height: number) => void;
-    onBrowseClick?: () => (ScriptField | undefined);
-    onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
+    onBrowseClick?: () => ScriptField | undefined;
+    onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     pointerEvents?: () => Opt<string>;
@@ -42,7 +42,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldType: { name: string }, fieldStr: string) {
-        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`;  //e.g., "<ImageBox {...props} fieldKey={"data} />"
+        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
     }
 
     @computed
@@ -75,23 +75,22 @@ export class FieldView extends React.Component<FieldViewProps> {
         //}
         else if (field instanceof DateField) {
             return <p>{field.date.toLocaleString()}</p>;
-        }
-        else if (field instanceof Doc) {
-            return <p><b>{field.title?.toString()}</b></p>;
-        }
-        else if (field instanceof List) {
-            return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : ""}  </div>;
+        } else if (field instanceof Doc) {
+            return (
+                <p>
+                    <b>{field.title?.toString()}</b>
+                </p>
+            );
+        } else if (field instanceof List) {
+            return <div> {field.length ? field.map(f => Field.toString(f)).join(', ') : ''} </div>;
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         else if (field instanceof WebField) {
             return <p>{Field.toString(field.url.href)}</p>;
-        }
-        else if (!(field instanceof Promise)) {
+        } else if (!(field instanceof Promise)) {
             return <p>{Field.toString(field)}</p>;
-        }
-        else {
-            return <p> {"Waiting for server..."} </p>;
+        } else {
+            return <p> {'Waiting for server...'} </p>;
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ffa839fcb..9590bcb15 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -148,8 +148,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
         cropping.y = NumCast(this.rootDoc.y);
-        cropping._width = anchw * (this.props.scaling?.() || 1);
-        cropping._height = anchh * (this.props.scaling?.() || 1);
+        cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
+        cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
         cropping.isLinkButton = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
@@ -384,7 +384,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     render() {
         TraceMobx();
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
-        const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.scaling?.() || 1)}px` : borderRad;
+        const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.NativeDimScaling?.() || 1)}px` : borderRad;
         return (
             <div
                 className="imageBox"
@@ -407,7 +407,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     PanelHeight={this.props.PanelHeight}
                     ScreenToLocalTransform={this.screenToLocalTransform}
                     select={emptyFunction}
-                    scaling={returnOne}
+                    NativeDimScaling={returnOne}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.removeDocument}
                     moveDocument={this.moveDocument}
@@ -420,7 +420,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         rootDoc={this.rootDoc}
                         scrollTop={0}
                         down={this._marqueeing}
-                        scaling={this.props.scaling}
+                        scaling={this.props.NativeDimScaling}
                         docView={this.props.docViewPath().slice(-1)[0]}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index c4b42301e..e9ed594d3 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -373,7 +373,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 runInAction(() => {
                     const localDelta = this.props
                         .ScreenToLocalTransform()
-                        .scale(this.props.scaling?.() || 1)
+                        .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
                     const fullWidth = this.layoutDoc[WidthSym]();
                     const mapWidth = fullWidth - this.sidebarWidth();
@@ -559,8 +559,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         // }
     };
 
-    panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
     opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
@@ -589,13 +589,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         //     docFilters={docFilters || this.props.docFilters}
         //     dontRenderDocuments={docFilters ? false : true}
         //     select={emptyFunction}
-        //     ContentScaling={returnOne}
         //     bringToFront={emptyFunction}
         //     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
         //     removeDocument={this.removeDocument}
         //     moveDocument={this.moveDocument}
         //     addDocument={this.sidebarAddDocument}
-        //     childPointerEvents={true}
+        //     childPointerEvents={"all"}
         //     pointerEvents={Doc.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return (
             <div className="mapBox" ref={this._ref}>
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 72569135b..630ae18f5 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -62,7 +62,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             setHeight={emptyFunction}
                             isAnnotationOverlay={false}
                             select={emptyFunction}
-                            scaling={returnOne}
+                            NativeDimScaling={returnOne}
                             isContentActive={returnTrue}
                             chromeHidden={true}
                             rootSelected={returnFalse}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fe18abba9..fad22d6e9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -104,8 +104,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = cropping[WidthSym]() * (this.props.scaling?.() || 1);
-        const anchh = cropping[HeightSym]() * (this.props.scaling?.() || 1);
+        const anchw = cropping[WidthSym]() * (this.props.NativeDimScaling?.() || 1);
+        const anchh = cropping[HeightSym]() * (this.props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
@@ -290,11 +290,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             (e, down, delta) => {
                 const localDelta = this.props
                     .ScreenToLocalTransform()
-                    .scale(this.props.scaling?.() || 1)
+                    .scale(this.props.NativeDimScaling?.() || 1)
                     .transformDirection(delta[0], delta[1]);
                 const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
                 const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.scaling?.() || 1)) / nativeWidth;
+                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
@@ -433,12 +433,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
     }
 
-    contentScaling = () => 1;
     isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected();
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
-        const scale = previewScale * (this.props.scaling?.() || 1);
+        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
         return (
             <div
                 className={'pdfBox'}
@@ -473,7 +472,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         removeDocument={this.removeDocument}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         crop={this.crop}
-                        ContentScaling={returnOne}
                     />
                 </div>
                 <SidebarAnnos
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 942072524..76a24d831 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -295,7 +295,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             isAnnotationOverlay={true}
                             select={emptyFunction}
                             isContentActive={returnFalse}
-                            scaling={returnOne}
+                            NativeDimScaling={returnOne}
                             whenChildContentsActiveChanged={emptyFunction}
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
@@ -317,7 +317,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 isAnnotationOverlay={true}
                                 select={emptyFunction}
                                 isContentActive={emptyFunction}
-                                scaling={returnOne}
+                                NativeDimScaling={returnOne}
                                 xPadding={25}
                                 yPadding={10}
                                 whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b1f7d8023..681f7c5b2 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -898,7 +898,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
 
-    scaling = () => this.props.scaling?.() || 1;
+    scaling = () => this.props.NativeDimScaling?.() || 1;
 
     panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
     panelHeight = () => (this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
@@ -911,7 +911,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             .scale(100 / this.heightPercent);
     };
 
-    marqueeFitScaling = () => ((this.props.scaling?.() || 1) * this.heightPercent) / 100;
+    marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
     marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
 
     timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
@@ -1124,7 +1124,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             docFilters={this.timelineDocFilter}
                             select={emptyFunction}
-                            scaling={returnOne}
+                            NativeDimScaling={returnOne}
                             whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                             removeDocument={this.removeDocument}
                             moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 05da8bd7b..71d6959a3 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -200,7 +200,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             autoHeight => {
                 if (autoHeight) {
                     this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']);
-                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
                 }
             }
         );
@@ -276,7 +276,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         if (doc !== this.rootDoc && this._outerRef.current) {
-            const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+            const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
             const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
             if (scrollTo !== undefined && this._initialScroll === undefined) {
                 const focusSpeed = smooth ? 500 : 0;
@@ -310,7 +310,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
             const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
-            const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+            const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
             const sel = this._iframe.contentWindow.getSelection();
             if (sel) {
                 this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
@@ -322,7 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     iframeDown = (e: PointerEvent) => {
         const sel = this._iframe?.contentWindow?.getSelection?.();
         const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
-        const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+        const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
         this._setPreviewCursor?.(e.clientX, e.clientY, false, true);
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
@@ -604,7 +604,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             funcs.push({
                 description: (!this.layoutDoc.forceReflow ? 'Force' : 'Prevent') + ' Reflow',
                 event: () => {
-                    const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
+                    const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
                     this.layoutDoc.forceReflow = !nw;
                     if (nw) {
                         Doc.SetInPlace(this.layoutDoc, this.fieldKey + '-nativeWidth', nw, true);
@@ -717,12 +717,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._draggingSidebar = true;
                 const localDelta = this.props
                     .ScreenToLocalTransform()
-                    .scale(this.props.scaling?.() || 1)
+                    .scale(this.props.NativeDimScaling?.() || 1)
                     .transformDirection(delta[0], delta[1]);
                 const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
                 const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight']);
                 const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.scaling?.() || 1)) / nativeWidth;
+                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
@@ -834,8 +834,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
-    panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
@@ -852,7 +852,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     render() {
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
-        const scale = previewScale * (this.props.scaling?.() || 1);
+        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
         const renderAnnotations = (docFilters?: () => string[]) => (
             <CollectionFreeFormView
                 {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
@@ -864,12 +864,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 PanelWidth={this.panelWidth}
                 PanelHeight={this.panelHeight}
                 ScreenToLocalTransform={this.scrollXf}
-                scaling={returnOne}
+                NativeDimScaling={returnOne}
                 dropAction={'alias'}
                 docFilters={docFilters || this.basicFilter}
                 dontRenderDocuments={docFilters ? false : true}
                 select={emptyFunction}
-                ContentScaling={returnOne}
                 bringToFront={emptyFunction}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
@@ -950,7 +949,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
                     setHeight={emptyFunction}
-                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.scaling?.() || 1)}
+                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
                     showSidebar={this.SidebarShown}
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 383c22ba8..e9b048c5e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -968,7 +968,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     @computed get contentScaling() {
-        return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;
+        return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1;
     }
     componentDidMount() {
         !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
@@ -1691,7 +1691,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     fitContentsToBox = () => this.props.Document._fitContentsToBox;
-    sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+    sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
         // console.log("printting allSideBarDocs");
@@ -1705,7 +1705,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     sidebarScreenToLocal = () =>
         this.props
             .ScreenToLocalTransform()
-            .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0)
+            .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
             .scale(1 / NumCast(this.layoutDoc._viewScale, 1));
 
     @computed get audioHandle() {
@@ -1768,7 +1768,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     scaleField={this.SidebarKey + '-scale'}
                     isAnnotationOverlay={false}
                     select={emptyFunction}
-                    scaling={this.sidebarContentScaling}
+                    NativeDimScaling={this.sidebarContentScaling}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.sidebarRemDocument}
                     moveDocument={this.sidebarMoveDocument}
@@ -1793,7 +1793,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         TraceMobx();
         const active = this.props.isContentActive() || this.props.isSelected();
         const selected = active;
-        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : '';
         const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index bd5e8caab..0cf15d297 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -199,11 +199,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
-        e.stopPropagation();
-        e.preventDefault();
-    };
-
     /**
      * Function to drag and drop the pres element to a diferent location
      */
@@ -477,8 +472,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 })}
                 onPointerOver={this.onPointerOver}
                 onPointerLeave={this.onPointerLeave}
-                onPointerDown={this.headerDown}
-                onPointerUp={this.headerUp}>
+                onPointerDown={this.headerDown}>
                 {/* {miniView ?
                               // when width is LESS than 110 px 
                               <div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 837734edf..2c83082b7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -41,7 +41,6 @@ interface IViewerProps extends FieldViewProps {
     url: string;
     loaded?: (nw: number, nh: number, np: number) => void;
     setPdfViewer: (view: PDFViewer) => void;
-    ContentScaling?: () => number;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
 }
@@ -97,7 +96,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             autoHeight => {
                 if (autoHeight) {
                     this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']);
-                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
                 }
             }
         );
@@ -162,7 +161,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
-            const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+            const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
             const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
             if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
                 focusSpeed = 500;
@@ -486,8 +485,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
-    panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
     opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
@@ -509,7 +508,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             PanelWidth={this.panelWidth}
             dropAction={'alias'}
             select={emptyFunction}
-            ContentScaling={this.contentZoom}
             bringToFront={emptyFunction}
             docFilters={docFilters || this.basicFilter}
             styleProvider={this.childStyleProvider}
@@ -556,10 +554,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @computed get pdfViewerDiv() {
         return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
     }
-    @computed get contentScaling() {
-        return this.props.ContentScaling?.() || 1;
-    }
-    contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1);
     savedAnnotations = () => this._savedAnnotations;
     render() {
         TraceMobx();
@@ -574,8 +568,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     onClick={this.onClick}
                     style={{
                         overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? 'scroll' : undefined,
-                        height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
-                        transform: `scale(${this.contentScaling})`,
+                        height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
                     }}>
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
-- 
cgit v1.2.3-70-g09d2


From 8757b03bfd6f3d33335f8e126163b1daa2376589 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 25 Jul 2022 10:55:07 -0400
Subject: made formatttedtextbox annotation handle work the same as pdf/web. 
 prevent header from hiding annotation button handle.  fixed resizing text
 boxes.

---
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/client/views/nodes/MapBox/MapBox.tsx           |  38 +-
 src/client/views/nodes/PDFBox.tsx                  |  33 +-
 src/client/views/nodes/WebBox.tsx                  |  29 +-
 .../nodes/formattedText/FormattedTextBox.scss      | 553 +++++++++++++++++----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  14 +-
 7 files changed, 510 insertions(+), 160 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 964fd36c8..4d38a8194 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -521,7 +521,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             } else doc._height = actualdH;
                         }
                     } else {
-                        const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight)) * docView.NativeDimScaling();
+                        const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
                         dH && (doc._height = actualdH > maxHeight ? maxHeight : actualdH);
                         dW && (doc._width = actualdW);
                         dH && (doc._autoHeight = false);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6bf0c365a..7032abba2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1229,6 +1229,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 style={{
                     position: this.headerMargin ? 'relative' : 'absolute',
                     height: this.titleHeight,
+                    width: this.isContentActive() ? `calc(100% - 18px)` : '100%', // leave room for annotation button
                     color: lightOrDark(background),
                     background,
                     pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index e9ed594d3..abd64908f 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -7,7 +7,6 @@ import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { TraceMobx } from '../../../../fields/util';
 import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
@@ -20,7 +19,6 @@ import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { Annotation } from '../../pdf/Annotation';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '../FieldView';
 import './MapBox.scss';
 import { MapBoxInfoWindow } from './MapBoxInfoWindow';
@@ -453,21 +451,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      * Handles toggle of sidebar on click the little comment button
      */
     @computed get sidebarHandle() {
-        TraceMobx();
-        const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
-        const color = !annotated ? Colors.WHITE : Colors.BLACK;
-        const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
-        return !annotated ? null : (
+        return (
             <div
-                className="formattedTextBox-sidebar-handle"
-                onPointerDown={this.sidebarDown}
+                className="mapBox-overlayButton-sidebar"
+                key="sidebar"
+                title="Toggle Sidebar"
                 style={{
-                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
-                    backgroundColor: backgroundColor,
-                    color: color,
-                    opacity: annotated ? 1 : undefined,
-                }}>
-                <FontAwesomeIcon icon={'comment-alt'} />
+                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+                }}
+                onPointerDown={this.sidebarBtnDown}>
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
             </div>
         );
     }
@@ -665,18 +660,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         removeDocument={this.sidebarRemoveDocument}
                     />
                 </div>
-                <div
-                    className="mapBox-overlayButton-sidebar"
-                    key="sidebar"
-                    title="Toggle Sidebar"
-                    style={{
-                        display: !this.props.isContentActive() ? 'none' : undefined,
-                        top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
-                        backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
-                    }}
-                    onPointerDown={this.sidebarBtnDown}>
-                    <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
-                </div>
+                {this.sidebarHandle}
             </div>
         );
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fad22d6e9..42df2ac5d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -8,7 +8,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { KeyCodes } from '../../util/KeyCodes';
@@ -23,8 +23,8 @@ import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
-import { VideoBox } from './VideoBox';
 import './PDFBox.scss';
+import { VideoBox } from './VideoBox';
 import React = require('react');
 
 @observer
@@ -390,18 +390,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     />
                     {this._pageControls ? pageBtns : null}
                 </div>
-                <div
-                    className="pdfBox-sidebarBtn"
-                    key="sidebar"
-                    title="Toggle Sidebar"
-                    style={{
-                        display: !this.props.isContentActive() ? 'none' : undefined,
-                        top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
-                        backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
-                    }}
-                    onPointerDown={e => this.sidebarBtnDown(e, true)}>
-                    <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
-                </div>
+                {this.sidebarHandle}
             </div>
         );
     }
@@ -432,6 +421,22 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get SidebarShown() {
         return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
     }
+    @computed get sidebarHandle() {
+        return (
+            <div
+                className="pdfBox-sidebarBtn"
+                key="sidebar"
+                title="Toggle Sidebar"
+                style={{
+                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+                }}
+                onPointerDown={e => this.sidebarBtnDown(e, true)}>
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+            </div>
+        );
+    }
 
     isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected();
     @computed get renderPdfView() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 71d6959a3..55228c19f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -741,6 +741,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
         );
     };
+    @computed get sidebarHandle() {
+        return (
+            <div
+                className="webBox-overlayButton-sidebar"
+                key="sidebar"
+                title="Toggle Sidebar"
+                style={{
+                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+                }}
+                onPointerDown={e => this.sidebarBtnDown(e, true)}>
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+            </div>
+        );
+    }
     @observable _previewNativeWidth: Opt<number> = undefined;
     @observable _previewWidth: Opt<number> = undefined;
     toggleSidebar = action((preview: boolean = false) => {
@@ -955,18 +971,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
                 />
-                <div
-                    className="webBox-overlayButton-sidebar"
-                    key="sidebar"
-                    title="Toggle Sidebar"
-                    style={{
-                        display: !this.props.isContentActive() ? 'none' : undefined,
-                        top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
-                        backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
-                    }}
-                    onPointerDown={e => this.sidebarBtnDown(e, true)}>
-                    <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
-                </div>
+                {this.sidebarHandle}
                 {!this.props.isContentActive() ? null : this.searchUI}
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 27817f317..d3d8c47c0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
 
 .ProseMirror {
     width: 100%;
@@ -11,13 +11,13 @@
 }
 
 audiotag {
-    left: 0; 
-    position: absolute; 
-    cursor: pointer; 
-    border-radius: 10px; 
-    width: 10px; 
-    margin-top: -2px; 
-    font-size: 4px; 
+    left: 0;
+    position: absolute;
+    cursor: pointer;
+    border-radius: 10px;
+    width: 10px;
+    margin-top: -2px;
+    font-size: 4px;
     background: lightblue;
 }
 audiotag:hover {
@@ -63,12 +63,11 @@ audiotag:hover {
 .formattedTextBox-outer-selected {
     cursor: text;
 }
-    
+
 .formattedTextBox-sidebar-handle {
     position: absolute;
     top: 0;
-    left: 17px;
-    //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
+    right: 0;
     width: 17px;
     height: 17px;
     font-size: 11px;
@@ -79,15 +78,14 @@ audiotag:hover {
     display: flex;
     justify-content: center;
     align-items: center;
-    cursor:grabbing;
+    cursor: grabbing;
     box-shadow: $standard-box-shadow;
     // transition: 0.2s;
     opacity: 0.3;
-    &:hover{
+    &:hover {
         opacity: 1 !important;
         filter: brightness(0.85);
     }
-
 }
 
 .formattedTextBox-sidebar,
@@ -117,14 +115,15 @@ audiotag:hover {
     left: 10%;
 }
 
-.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner, 
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner,
 .formattedTextBox-inner-minimal,
 .formattedTextBox-inner-selected {
     height: 100%;
     white-space: pre-wrap;
     .ProseMirror:hover {
-        background: rgba(200,200,200,0.2);
+        background: rgba(200, 200, 200, 0.2);
     }
     hr {
         display: block;
@@ -141,7 +140,7 @@ audiotag:hover {
 .formattedTextBox-inner-rounded-selected,
 .formattedTextBox-inner-selected {
     > .ProseMirror {
-        padding:10px;
+        padding: 10px;
     }
 }
 .formattedTextBox-outer-selected {
@@ -236,18 +235,17 @@ footnote::after {
     position: absolute;
     top: -5px;
     left: 27px;
-    content: " ";
+    content: ' ';
     height: 0;
     width: 0;
 }
 
-
 .formattedTextBox-inlineComment {
     position: relative;
     width: 40px;
     height: 20px;
     &::before {
-        content: "→";
+        content: '→';
     }
     &:hover {
         background: orange;
@@ -260,7 +258,7 @@ footnote::after {
     width: 40px;
     height: 20px;
     &::after {
-        content: "←";
+        content: '←';
     }
 }
 
@@ -270,21 +268,21 @@ footnote::after {
     width: 40px;
     height: 20px;
     &::after {
-        content: "...";
+        content: '...';
     }
 }
 
 .prosemirror-anchor {
-    overflow:hidden;
-    display:inline-grid;
+    overflow: hidden;
+    display: inline-grid;
 }
 .prosemirror-linkBtn {
-    background:unset;
-    color:unset;
-    padding:0;
+    background: unset;
+    color: unset;
+    padding: 0;
     text-transform: unset;
     letter-spacing: unset;
-    font-size:unset;
+    font-size: unset;
 }
 .prosemirror-links {
     display: none;
@@ -294,28 +292,28 @@ footnote::after {
     z-index: 1;
     padding: 5;
     border-radius: 2px;
-  }
-  .prosemirror-hrefoptions{
-      width:0px;
-      border:unset;
-      padding:0px;
-  }
-
-  .prosemirror-links a {
+}
+.prosemirror-hrefoptions {
+    width: 0px;
+    border: unset;
+    padding: 0px;
+}
+
+.prosemirror-links a {
     float: left;
     color: white;
     text-decoration: none;
     border-radius: 3px;
-  }
+}
 
-  .prosemirror-links a:hover {
+.prosemirror-links a:hover {
     background-color: #eee;
     color: black;
-  }
+}
 
-  .prosemirror-anchor:hover .prosemirror-links {
+.prosemirror-anchor:hover .prosemirror-links {
     display: grid;
-  }
+}
 
 .ProseMirror {
     padding: 0px;
@@ -334,7 +332,8 @@ footnote::after {
         border-left: solid 2px dimgray;
     }
 
-    ol, ul {
+    ol,
+    ul {
         counter-reset: deci1 0 multi1 0;
         padding-left: 1em;
         font-family: inherit;
@@ -342,42 +341,231 @@ footnote::after {
     ol {
         font-family: inherit;
     }
-    .bullet {  p { font-family: inherit} margin-left: 0; }
-    .bullet1 { p { font-family: inherit} }
-    .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p { font-family: inherit} font-size: smaller; }
+    .bullet {
+        p {
+            font-family: inherit;
+        }
+        margin-left: 0;
+    }
+    .bullet1 {
+        p {
+            font-family: inherit;
+        }
+    }
+    .bullet2,
+    .bullet3,
+    .bullet4,
+    .bullet5,
+    .bullet6 {
+        p {
+            font-family: inherit;
+        }
+        font-size: smaller;
+    }
 
-    .decimal1-ol { counter-reset: deci1; p {display: inline-block; font-family: inherit} margin-left: 0; }
-    .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.1em;}
-    .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
-    .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
-    .decimal5-ol { counter-reset: deci5; p {display: inline-block; font-family: inherit} font-size: smaller; }
-    .decimal6-ol { counter-reset: deci6; p {display: inline-block; font-family: inherit} font-size: smaller; }
-    .decimal7-ol { counter-reset: deci7; p {display: inline-block; font-family: inherit} font-size: smaller; }
+    .decimal1-ol {
+        counter-reset: deci1;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        margin-left: 0;
+    }
+    .decimal2-ol {
+        counter-reset: deci2;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 2.1em;
+    }
+    .decimal3-ol {
+        counter-reset: deci3;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 2.85em;
+    }
+    .decimal4-ol {
+        counter-reset: deci4;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 3.85em;
+    }
+    .decimal5-ol {
+        counter-reset: deci5;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+    }
+    .decimal6-ol {
+        counter-reset: deci6;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+    }
+    .decimal7-ol {
+        counter-reset: deci7;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+    }
 
-    .multi1-ol { counter-reset: multi1; p {display: inline-block; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
-    .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
-    .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
+    .multi1-ol {
+        counter-reset: multi1;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        margin-left: 0;
+        padding-left: 1.2em;
+    }
+    .multi2-ol {
+        counter-reset: multi2;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 2em;
+    }
+    .multi3-ol {
+        counter-reset: multi3;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 2.85em;
+    }
+    .multi4-ol {
+        counter-reset: multi4;
+        p {
+            display: inline-block;
+            font-family: inherit;
+        }
+        font-size: smaller;
+        padding-left: 3.85em;
+    }
 
     //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
 
-    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; vertical-align: top; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
-    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; vertical-align: top; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
-    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; vertical-align: top; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
-    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; vertical-align: top; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
-    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; vertical-align: top; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
-    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; vertical-align: top; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
-    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; vertical-align: top; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+    .decimal1:before {
+        transition: 0.5s;
+        counter-increment: deci1;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -1em;
+        width: 1em;
+        content: counter(deci1) '. ';
+    }
+    .decimal2:before {
+        transition: 0.5s;
+        counter-increment: deci2;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2.1em;
+        width: 2.1em;
+        content: counter(deci1) '.' counter(deci2) '. ';
+    }
+    .decimal3:before {
+        transition: 0.5s;
+        counter-increment: deci3;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2.85em;
+        width: 2.85em;
+        content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '. ';
+    }
+    .decimal4:before {
+        transition: 0.5s;
+        counter-increment: deci4;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -3.85em;
+        width: 3.85em;
+        content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. ';
+    }
+    .decimal5:before {
+        transition: 0.5s;
+        counter-increment: deci5;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2em;
+        width: 5em;
+        content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '. ';
+    }
+    .decimal6:before {
+        transition: 0.5s;
+        counter-increment: deci6;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2em;
+        width: 6em;
+        content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '. ';
+    }
+    .decimal7:before {
+        transition: 0.5s;
+        counter-increment: deci7;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2em;
+        width: 7em;
+        content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '.' counter(deci7) '. ';
+    }
 
-    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1.3em;  width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
-    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
-    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
-    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; vertical-align: top; margin-left: -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+    .multi1:before {
+        transition: 0.5s;
+        counter-increment: multi1;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -1.3em;
+        width: 1.2em;
+        content: counter(multi1, upper-alpha) '. ';
+    }
+    .multi2:before {
+        transition: 0.5s;
+        counter-increment: multi2;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2em;
+        width: 2em;
+        content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '. ';
+    }
+    .multi3:before {
+        transition: 0.5s;
+        counter-increment: multi3;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -2.85em;
+        width: 2.85em;
+        content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '. ';
+    }
+    .multi4:before {
+        transition: 0.5s;
+        counter-increment: multi4;
+        display: inline-block;
+        vertical-align: top;
+        margin-left: -4.2em;
+        width: 4.2em;
+        content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '.' counter(multi4, lower-roman) '. ';
+    }
 }
 
-
 @media only screen and (max-width: 1000px) {
-    @import "../../global/globalCssVariables";
+    @import '../../global/globalCssVariables';
 
     .ProseMirror {
         width: 100%;
@@ -425,7 +613,7 @@ footnote::after {
         width: 100%;
         height: 100%;
     }
-    
+
     .formattedTextBox-sidebar-handle {
         position: absolute;
         background: lightgray;
@@ -562,18 +750,17 @@ footnote::after {
         position: absolute;
         top: -5px;
         left: 27px;
-        content: " ";
+        content: ' ';
         height: 0;
         width: 0;
     }
 
-
     .formattedTextBox-inlineComment {
         position: relative;
         width: 40px;
         height: 20px;
         &::before {
-            content: "→";
+            content: '→';
         }
         &:hover {
             background: orange;
@@ -586,7 +773,7 @@ footnote::after {
         width: 40px;
         height: 20px;
         &::after {
-            content: "←";
+            content: '←';
         }
     }
 
@@ -596,7 +783,7 @@ footnote::after {
         width: 40px;
         height: 20px;
         &::after {
-            content: "...";
+            content: '...';
         }
     }
 
@@ -606,7 +793,8 @@ footnote::after {
             font-family: inherit;
         }
 
-        ol, ul {
+        ol,
+        ul {
             counter-reset: deci1 0 multi1 0;
             padding-left: 1em;
             font-family: inherit;
@@ -616,30 +804,191 @@ footnote::after {
             font-family: inherit;
         }
 
-        .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
-        .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
-        .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-        .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
-        .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
-        .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
-        .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
-
-        .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
-        .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
-        .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-        .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
-
-        .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
-        .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
-        .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
-        .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
-        .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
-        .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
-        .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
-
-        .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
-        .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
-        .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
-        .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+        .decimal1-ol {
+            counter-reset: deci1;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            margin-left: 0;
+        }
+        .decimal2-ol {
+            counter-reset: deci2;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 1em;
+        }
+        .decimal3-ol {
+            counter-reset: deci3;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 2em;
+        }
+        .decimal4-ol {
+            counter-reset: deci4;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 3em;
+        }
+        .decimal5-ol {
+            counter-reset: deci5;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+        }
+        .decimal6-ol {
+            counter-reset: deci6;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+        }
+        .decimal7-ol {
+            counter-reset: deci7;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+        }
+
+        .multi1-ol {
+            counter-reset: multi1;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            margin-left: 0;
+            padding-left: 1.2em;
+        }
+        .multi2-ol {
+            counter-reset: multi2;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 1.4em;
+        }
+        .multi3-ol {
+            counter-reset: multi3;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 2em;
+        }
+        .multi4-ol {
+            counter-reset: multi4;
+            p {
+                display: inline;
+                font-family: inherit;
+            }
+            font-size: smaller;
+            padding-left: 3.4em;
+        }
+
+        .decimal1:before {
+            transition: 0.5s;
+            counter-increment: deci1;
+            display: inline-block;
+            margin-left: -1em;
+            width: 1em;
+            content: counter(deci1) '. ';
+        }
+        .decimal2:before {
+            transition: 0.5s;
+            counter-increment: deci2;
+            display: inline-block;
+            margin-left: -2.1em;
+            width: 2.1em;
+            content: counter(deci1) '.' counter(deci2) '. ';
+        }
+        .decimal3:before {
+            transition: 0.5s;
+            counter-increment: deci3;
+            display: inline-block;
+            margin-left: -2.85em;
+            width: 2.85em;
+            content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '. ';
+        }
+        .decimal4:before {
+            transition: 0.5s;
+            counter-increment: deci4;
+            display: inline-block;
+            margin-left: -3.85em;
+            width: 3.85em;
+            content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. ';
+        }
+        .decimal5:before {
+            transition: 0.5s;
+            counter-increment: deci5;
+            display: inline-block;
+            margin-left: -2em;
+            width: 5em;
+            content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '. ';
+        }
+        .decimal6:before {
+            transition: 0.5s;
+            counter-increment: deci6;
+            display: inline-block;
+            margin-left: -2em;
+            width: 6em;
+            content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '. ';
+        }
+        .decimal7:before {
+            transition: 0.5s;
+            counter-increment: deci7;
+            display: inline-block;
+            margin-left: -2em;
+            width: 7em;
+            content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '.' counter(deci7) '. ';
+        }
+
+        .multi1:before {
+            transition: 0.5s;
+            counter-increment: multi1;
+            display: inline-block;
+            margin-left: -1em;
+            width: 1.2em;
+            content: counter(multi1, upper-alpha) '. ';
+        }
+        .multi2:before {
+            transition: 0.5s;
+            counter-increment: multi2;
+            display: inline-block;
+            margin-left: -2em;
+            width: 2em;
+            content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '. ';
+        }
+        .multi3:before {
+            transition: 0.5s;
+            counter-increment: multi3;
+            display: inline-block;
+            margin-left: -2.85em;
+            width: 2.85em;
+            content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '. ';
+        }
+        .multi4:before {
+            transition: 0.5s;
+            counter-increment: multi4;
+            display: inline-block;
+            margin-left: -4.2em;
+            width: 4.2em;
+            content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '.' counter(multi4, lower-roman) '. ';
+        }
     }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e9b048c5e..ead086aca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -61,6 +61,7 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
+import { text } from 'body-parser';
 const translateGoogleApi = require('translate-google-api');
 
 export interface FormattedTextBoxProps {
@@ -654,8 +655,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
-        const bounds = this._ref.current!.getBoundingClientRect();
-        this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+        const localDelta = this.props
+            .ScreenToLocalTransform()
+            .scale(this.props.NativeDimScaling?.() || 1)
+            .transformDirection(delta[0], delta[1]);
+        const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100;
+        const width = this.layoutDoc[WidthSym]() + localDelta[0];
+        this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
+        this.layoutDoc.width = width;
         this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';
         e.preventDefault();
         return false;
@@ -1726,7 +1733,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 className="formattedTextBox-sidebar-handle"
                 onPointerDown={this.sidebarDown}
                 style={{
-                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
                     backgroundColor: backgroundColor,
                     color: color,
                     opacity: annotated ? 1 : undefined,
-- 
cgit v1.2.3-70-g09d2


From dc0ecb47cd4e63e4861b79c00fe131de4450f46b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Jul 2022 09:27:12 -0400
Subject: turned off resetriting text box resizing to max of scroll height. 
 fixed placement/behavior of sidebar anno button for text.

---
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/SidebarAnnos.tsx                  |  8 +++---
 src/client/views/nodes/MapBox/MapBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 30 ++++++++++----------
 src/client/views/nodes/WebBox.tsx                  | 32 ++++++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++++--
 7 files changed, 45 insertions(+), 38 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 922f037f4..432dd360a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -521,7 +521,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             } else doc._height = actualdH;
                         }
                     } else {
-                        const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
+                        const maxHeight = 0; //Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
                         dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
                         dW && (doc._width = actualdW);
                         dH && (doc._autoHeight = false);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7e032af5e..a85460137 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -159,6 +159,7 @@ export class MainView extends React.Component {
                 'viewTransition',
                 'panX',
                 'panY',
+                'fitWidth',
                 'nativeWidth',
                 'nativeHeight',
                 'text-scrollHeight',
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 9fdf9d2be..1c14c7cd5 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -101,7 +101,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         !this.props.showSidebar
             ? 0
             : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP
-            ? this.props.PanelWidth()
+            ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
             : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
     panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
     addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
@@ -144,14 +144,14 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                     top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._showTitle) === 'title' ? 15 : 0,
                     right: 0,
                     background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
-                    width: `${this.panelWidth()}px`,
+                    width: `100%`,
                     height: '100%',
                 }}>
-                <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight(), width: this.panelWidth() }} onWheel={e => e.stopPropagation()}>
+                <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
                     {this.allUsers.map(renderUsers)}
                     {this.allMetadata.map(renderMeta)}
                 </div>
-                <div style={{ width: '100%', height: this.panelHeight(), position: 'relative' }}>
+                <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
                         {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                         ref={this._stackRef}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index abd64908f..6479e933e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -643,7 +643,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     )}
                 </div>
                 {/* </LoadScript > */}
-                <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                <div className="mapBox-sidebar" style={{ position: 'absolute', right: 0, height: '100%', width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
                         {...this.props}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 42df2ac5d..345407c2f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -479,20 +479,22 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         crop={this.crop}
                     />
                 </div>
-                <SidebarAnnos
-                    ref={this._sidebarRef}
-                    {...this.props}
-                    rootDoc={this.rootDoc}
-                    layoutDoc={this.layoutDoc}
-                    dataDoc={this.dataDoc}
-                    setHeight={emptyFunction}
-                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
-                    showSidebar={this.SidebarShown}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    sidebarAddDocument={this.sidebarAddDocument}
-                    moveDocument={this.moveDocument}
-                    removeDocument={this.removeDocument}
-                />
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.layoutDoc[WidthSym]()}%` }}>
+                    <SidebarAnnos
+                        ref={this._sidebarRef}
+                        {...this.props}
+                        rootDoc={this.rootDoc}
+                        layoutDoc={this.layoutDoc}
+                        dataDoc={this.dataDoc}
+                        setHeight={emptyFunction}
+                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+                        showSidebar={this.SidebarShown}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        sidebarAddDocument={this.sidebarAddDocument}
+                        moveDocument={this.moveDocument}
+                        removeDocument={this.removeDocument}
+                    />
+                </div>
                 {this.settingsPanel()}
             </div>
         );
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 55228c19f..dd14822af 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -956,21 +956,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     }}
                     onPointerDown={e => this.sidebarBtnDown(e, false)}
                 />
-                <SidebarAnnos
-                    ref={this._sidebarRef}
-                    {...this.props}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    fieldKey={this.fieldKey + '-' + this._urlHash}
-                    rootDoc={this.rootDoc}
-                    layoutDoc={this.layoutDoc}
-                    dataDoc={this.dataDoc}
-                    setHeight={emptyFunction}
-                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
-                    showSidebar={this.SidebarShown}
-                    sidebarAddDocument={this.sidebarAddDocument}
-                    moveDocument={this.moveDocument}
-                    removeDocument={this.removeDocument}
-                />
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.layoutDoc[WidthSym]()}%` }}>
+                    <SidebarAnnos
+                        ref={this._sidebarRef}
+                        {...this.props}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        fieldKey={this.fieldKey + '-' + this._urlHash}
+                        rootDoc={this.rootDoc}
+                        layoutDoc={this.layoutDoc}
+                        dataDoc={this.dataDoc}
+                        setHeight={emptyFunction}
+                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
+                        showSidebar={this.SidebarShown}
+                        sidebarAddDocument={this.sidebarAddDocument}
+                        moveDocument={this.moveDocument}
+                        removeDocument={this.removeDocument}
+                    />
+                </div>
                 {this.sidebarHandle}
                 {!this.props.isContentActive() ? null : this.searchUI}
             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ead086aca..fe121471a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -124,6 +124,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return DocListCast(this.dataDoc[this.SidebarKey]);
     }
 
+    @computed get noSidebar() {
+        return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar;
+    }
     @computed get sidebarWidthPercent() {
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
     }
@@ -1752,7 +1755,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
-                    // usePanelWidth={true}
                     nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     showSidebar={this.SidebarShown}
@@ -1866,8 +1868,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                             }}
                         />
                     </div>
-                    {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
-                    {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
+                    {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+                    {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
                     {!this.layoutDoc._showAudio ? null : this.audioHandle}
                 </div>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 2f9ed7522d0615dfc27b7382be8b66eb341722da Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Jul 2022 11:16:39 -0400
Subject: made width a playground field so shared views can open annotation
 pane.  made title shrink for annotation button only when title doesn't add a
 margin.  Fixed closing annotation view in lightbox not to make docuent shrink
 to minimum size by using percents, not pixels.

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

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a85460137..4dc8a2e4a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -171,6 +171,7 @@ export class MainView extends React.Component {
                 'curPage',
                 'viewType',
                 'chromeHidden',
+                'width',
                 'nativeWidth',
             ]); // can play with these fields on someone else's
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7032abba2..2ad8b8af0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1229,7 +1229,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 style={{
                     position: this.headerMargin ? 'relative' : 'absolute',
                     height: this.titleHeight,
-                    width: this.isContentActive() ? `calc(100% - 18px)` : '100%', // leave room for annotation button
+                    width: !this.headerMargin ? `calc(100% - 18px)` : '100%', // leave room for annotation button
                     color: lightOrDark(background),
                     background,
                     pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fe121471a..bbb7ddc85 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -637,11 +637,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @action
     toggleSidebar = (preview: boolean = false) => {
-        const prevWidth = this.sidebarWidth();
+        const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
         if (preview) this._showSidebar = true;
         else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
 
-        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
         const batch = UndoManager.StartBatch('sidebar');
-- 
cgit v1.2.3-70-g09d2


From 131408385d49fc8d5f360c2918737da4cecc13c1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Jul 2022 16:55:01 -0400
Subject: fixed autoHeight for texdtviews embedded in text views.   fixed doc
 decorations for sidebar items in lightbox view.  added 'tree' as sidebar type
 for text.  fixed text sidebar to show same annotations whether in stacking or
 tree view.   fixed linkDescription pop to go away on click outside of it.

---
 src/client/views/EditableView.tsx                  | 113 ++++++++++++---------
 src/client/views/nodes/LinkDescriptionPopup.tsx    |  74 +++++++-------
 .../views/nodes/formattedText/DashDocView.tsx      |  30 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  64 ++++++------
 .../views/nodes/formattedText/RichTextMenu.tsx     |   2 +-
 5 files changed, 158 insertions(+), 125 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d7707a6fe..8036df471 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Autosuggest from 'react-autosuggest';
 import { ObjectField } from '../../fields/ObjectField';
-import "./EditableView.scss";
+import './EditableView.scss';
 
 export interface EditableProps {
     /**
@@ -26,17 +26,16 @@ export interface EditableProps {
     contents: any;
     fontStyle?: string;
     fontSize?: number;
-    height?: number | "auto";
+    height?: number | 'auto';
     sizeToContent?: boolean;
     maxHeight?: number;
     display?: string;
     overflow?: string;
     autosuggestProps?: {
         resetValue: () => void;
-        value: string,
-        onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
-        autosuggestProps: Autosuggest.AutosuggestProps<string, any>
-
+        value: string;
+        onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void;
+        autosuggestProps: Autosuggest.AutosuggestProps<string, any>;
     };
     oneLine?: boolean;
     editing?: boolean;
@@ -81,16 +80,16 @@ export class EditableView extends React.Component<EditableProps> {
     @action
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         switch (e.key) {
-            case "Tab":
+            case 'Tab':
                 e.stopPropagation();
                 this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
                 this.props.OnTab?.(e.shiftKey);
                 break;
-            case "Backspace":
+            case 'Backspace':
                 e.stopPropagation();
                 if (!e.currentTarget.value) this.props.OnEmpty?.();
                 break;
-            case "Enter":
+            case 'Enter':
                 e.stopPropagation();
                 if (!e.ctrlKey) {
                     this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
@@ -100,22 +99,26 @@ export class EditableView extends React.Component<EditableProps> {
                     this.props.isEditingCallback?.(false);
                 }
                 break;
-            case "Escape":
+            case 'Escape':
                 e.stopPropagation();
                 this._editing = false;
                 this.props.isEditingCallback?.(false);
                 break;
-            case ":":
+            case ':':
                 this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                 break;
-            case "Shift": case "Alt": case "Meta": case "Control": break;
+            case 'Shift':
+            case 'Alt':
+            case 'Meta':
+            case 'Control':
+                break;
             default:
                 if (this.props.textCallback?.(e.key)) {
                     this._editing = false;
-                    this.props.isEditingCallback?.(false,);
+                    this.props.isEditingCallback?.(false);
                 }
         }
-    }
+    };
 
     @action
     onClick = (e: React.MouseEvent) => {
@@ -129,40 +132,44 @@ export class EditableView extends React.Component<EditableProps> {
             }
             e.stopPropagation();
         }
-    }
+    };
 
     @action
     finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
         if (this.props.SetValue(value, shiftDown, enterKey)) {
             this._editing = false;
-            this.props.isEditingCallback?.(false,);
+            this.props.isEditingCallback?.(false);
         } else {
             this._editing = false;
             this.props.isEditingCallback?.(false);
-            !lostFocus && setTimeout(action(() => {
-                this._editing = true;
-                this.props.isEditingCallback?.(true);
-            }), 0);
+            !lostFocus &&
+                setTimeout(
+                    action(() => {
+                        this._editing = true;
+                        this.props.isEditingCallback?.(true);
+                    }),
+                    0
+                );
         }
     }
 
-    stopPropagation(e: React.SyntheticEvent) { e.stopPropagation(); }
+    stopPropagation(e: React.SyntheticEvent) {
+        e.stopPropagation();
+    }
 
     @action
     setIsFocused = (value: boolean) => {
         const wasFocused = this._editing;
         this._editing = value;
         return wasFocused !== this._editing;
-    }
-
-
+    };
 
     renderEditor() {
-        return this.props.autosuggestProps
-            ? <Autosuggest
+        return this.props.autosuggestProps ? (
+            <Autosuggest
                 {...this.props.autosuggestProps.autosuggestProps}
                 inputProps={{
-                    className: "editableView-input",
+                    className: 'editableView-input',
                     onKeyDown: this.onKeyDown,
                     autoFocus: true,
                     onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false),
@@ -171,39 +178,49 @@ export class EditableView extends React.Component<EditableProps> {
                     onPointerUp: this.stopPropagation,
                     onKeyPress: this.stopPropagation,
                     value: this.props.autosuggestProps.value,
-                    onChange: this.props.autosuggestProps.onChange
+                    onChange: this.props.autosuggestProps.onChange,
                 }}
             />
-            : <input className="editableView-input" ref={this._inputref}
-                style={{ display: this.props.display, overflow: "auto", fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
+        ) : (
+            <input
+                className="editableView-input"
+                ref={this._inputref}
+                style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
                 placeholder={this.props.placeholder}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
                 defaultValue={this.props.GetValue()}
                 autoFocus={true}
                 onKeyDown={this.onKeyDown}
-                onKeyPress={this.stopPropagation} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
-            />;
+                onKeyPress={this.stopPropagation}
+                onPointerDown={this.stopPropagation}
+                onClick={this.stopPropagation}
+                onPointerUp={this.stopPropagation}
+            />
+        );
     }
 
     render() {
         if (this._editing && this.props.GetValue() !== undefined) {
-            return this.props.sizeToContent ?
-                <div style={{ display: "grid", minWidth: 100 }}>
-                    <div style={{ display: "inline-block", position: "relative", height: 0, width: "100%", overflow: "hidden" }}>
-                        {this.props.GetValue()}
-                    </div>
+            return this.props.sizeToContent ? (
+                <div style={{ display: 'grid', minWidth: 100 }}>
+                    <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.props.GetValue()}</div>
                     {this.renderEditor()}
-                </div> :
-                this.renderEditor();
+                </div>
+            ) : (
+                this.renderEditor()
+            );
         }
         setTimeout(() => this.props.autosuggestProps?.resetValue());
-        return this.props.contents instanceof ObjectField ? (null) :
-            <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} ref={this._ref}
-                style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: "10px", whiteSpace: "nowrap", height: this.props.height || "auto", maxHeight: this.props.maxHeight }}
-                onClick={this.onClick} placeholder={this.props.placeholder}>
-                <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }} >
-                    {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
-                </span>
-            </div>;
+        return this.props.contents instanceof ObjectField ? null : (
+            <div
+                className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
+                ref={this._ref}
+                style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: 'nowrap', height: this.props.height || 'auto', maxHeight: this.props.maxHeight }}
+                onPointerDown={e => e.stopPropagation()}
+                onClick={this.onClick}
+                placeholder={this.props.placeholder}>
+                <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+            </div>
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index ccac66996..91bd505c5 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,26 +1,24 @@
-import React = require("react");
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { LinkManager } from "../../util/LinkManager";
-import "./LinkDescriptionPopup.scss";
-import { TaskCompletionBox } from "./TaskCompletedBox";
-
+import React = require('react');
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { LinkManager } from '../../util/LinkManager';
+import './LinkDescriptionPopup.scss';
+import { TaskCompletionBox } from './TaskCompletedBox';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
-
     @observable public static descriptionPopup: boolean = false;
-    @observable public static showDescriptions: string = "ON";
+    @observable public static showDescriptions: string = 'ON';
     @observable public static popupX: number = 700;
     @observable public static popupY: number = 350;
-    @observable description: string = "";
+    @observable description: string = '';
     @observable popupRef = React.createRef<HTMLDivElement>();
 
     @action
     descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.description = e.currentTarget.value;
-    }
+    };
 
     @action
     onDismiss = (add: boolean) => {
@@ -28,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
         if (add) {
             LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description);
         }
-    }
+    };
 
     @action
     onClick = (e: PointerEvent) => {
@@ -36,35 +34,43 @@ export class LinkDescriptionPopup extends React.Component<{}> {
             LinkDescriptionPopup.descriptionPopup = false;
             TaskCompletionBox.taskCompleted = false;
         }
-    }
+    };
 
     @action
     componentDidMount() {
-        document.addEventListener("pointerdown", this.onClick);
+        document.addEventListener('pointerdown', this.onClick, true);
     }
 
     componentWillUnmount() {
-        document.removeEventListener("pointerdown", this.onClick);
+        document.removeEventListener('pointerdown', this.onClick, true);
     }
 
     render() {
-        return <div className="linkDescriptionPopup" ref={this.popupRef}
-            style={{
-                left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
-                top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
-            }}>
-            <input className="linkDescriptionPopup-input"
-                onKeyDown={e => e.stopPropagation()}
-                onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
-                placeholder={"(Optional) Enter link description..."}
-                onChange={(e) => this.descriptionChanged(e)}>
-            </input>
-            <div className="linkDescriptionPopup-btn">
-                <div className="linkDescriptionPopup-btn-dismiss"
-                    onPointerDown={e => this.onDismiss(false)}> Dismiss </div>
-                <div className="linkDescriptionPopup-btn-add"
-                    onPointerDown={e => this.onDismiss(true)}> Add </div>
+        return (
+            <div
+                className="linkDescriptionPopup"
+                ref={this.popupRef}
+                style={{
+                    left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+                    top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+                }}>
+                <input
+                    className="linkDescriptionPopup-input"
+                    onKeyDown={e => e.stopPropagation()}
+                    onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
+                    placeholder={'(Optional) Enter link description...'}
+                    onChange={e => this.descriptionChanged(e)}></input>
+                <div className="linkDescriptionPopup-btn">
+                    <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+                        {' '}
+                        Dismiss{' '}
+                    </div>
+                    <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+                        {' '}
+                        Add{' '}
+                    </div>
+                </div>
             </div>
-        </div>;
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 08f255cab..73a711b9d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -70,6 +70,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     @observable _dashDoc: Doc | undefined;
     @observable _finalLayout: any;
     @observable _resolvedDataDoc: any;
+    @observable _width: number = 0;
+    @observable _height: number = 0;
 
     updateDoc = action((dashDoc: Doc) => {
         this._dashDoc = dashDoc;
@@ -83,12 +85,14 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
         }
         if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') {
             try {
+                this._width = NumCast(this._dashDoc?._width);
+                this._height = NumCast(this._dashDoc?._height);
                 // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
                 this.props.view.dispatch(
                     this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
                         ...this.props.node.attrs,
-                        width: (this._dashDoc?._width ?? '') + 'px',
-                        height: (this._dashDoc?._height ?? '') + 'px',
+                        width: this._width + 'px',
+                        height: this._height + 'px',
                     })
                 );
             } catch (e) {
@@ -121,17 +125,17 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     componentDidMount() {
         this._disposers.upater = reaction(
             () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width),
-            () => {
+            action(() => {
                 if (this._dashDoc) {
-                    this.props.view.dispatch(
-                        this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
-                            ...this.props.node.attrs,
-                            width: (this._dashDoc?._width ?? '') + 'px',
-                            height: (this._dashDoc?._height ?? '') + 'px',
-                        })
-                    );
+                    this._width = NumCast(this._dashDoc._width);
+                    this._height = NumCast(this._dashDoc._height);
+                    const parent = this._spanRef.current?.parentNode as HTMLElement;
+                    if (parent) {
+                        parent.style.width = this._width + 'px';
+                        parent.style.height = this._height + 'px';
+                    }
                 }
-            }
+            })
         );
     }
 
@@ -172,8 +176,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                 ref={this._spanRef}
                 className="dash-span"
                 style={{
-                    width: this.props.width,
-                    height: this.props.height,
+                    width: this._width,
+                    height: this._height,
                     position: 'absolute',
                     display: 'inline-block',
                 }}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bbb7ddc85..eb87d11a4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -20,7 +20,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -62,6 +62,7 @@ import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { text } from 'body-parser';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
 const translateGoogleApi = require('translate-google-api');
 
 export interface FormattedTextBoxProps {
@@ -1500,7 +1501,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     onFocused = (e: React.FocusEvent): void => {
         //applyDevTools.applyDevTools(this._editorView);
         FormattedTextBox.Focused = this;
-        this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+        this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
     };
     @observable public static Focused: FormattedTextBox | undefined;
 
@@ -1588,6 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @action
     onBlur = (e: any) => {
+        if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
         this.autoLink();
         FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
         if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
@@ -1702,7 +1704,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
     fitContentsToBox = () => this.props.Document._fitContentsToBox;
     sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
-    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
         // console.log("printting allSideBarDocs");
         // console.log(this.allSidebarDocs);
@@ -1716,7 +1718,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.props
             .ScreenToLocalTransform()
             .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
-            .scale(1 / NumCast(this.layoutDoc._viewScale, 1));
+            .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));
 
     @computed get audioHandle() {
         return (
@@ -1746,7 +1748,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
-            const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+            const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'tree' ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
@@ -1755,6 +1757,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
                     nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     showSidebar={this.SidebarShown}
@@ -1765,30 +1768,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <ComponentTag
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                    NativeWidth={returnZero}
-                    NativeHeight={returnZero}
-                    PanelHeight={this.props.PanelHeight}
-                    PanelWidth={this.sidebarWidth}
-                    xPadding={0}
-                    yPadding={0}
-                    scaleField={this.SidebarKey + '-scale'}
-                    isAnnotationOverlay={false}
-                    select={emptyFunction}
-                    NativeDimScaling={this.sidebarContentScaling}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    removeDocument={this.sidebarRemDocument}
-                    moveDocument={this.sidebarMoveDocument}
-                    addDocument={this.sidebarAddDocument}
-                    CollectionView={undefined}
-                    ScreenToLocalTransform={this.sidebarScreenToLocal}
-                    renderDepth={this.props.renderDepth + 1}
-                    setHeight={this.setSidebarHeight}
-                    fitContentsToBox={this.fitContentsToBox}
-                    noSidebar={true}
-                    fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`}
-                />
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!), true)}>
+                    <ComponentTag
+                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
+                        PanelHeight={this.props.PanelHeight}
+                        PanelWidth={this.sidebarWidth}
+                        xPadding={0}
+                        yPadding={0}
+                        scaleField={this.SidebarKey + '-scale'}
+                        isAnnotationOverlay={false}
+                        select={emptyFunction}
+                        NativeDimScaling={this.sidebarContentScaling}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        removeDocument={this.sidebarRemDocument}
+                        moveDocument={this.sidebarMoveDocument}
+                        addDocument={this.sidebarAddDocument}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={this.sidebarScreenToLocal}
+                        renderDepth={this.props.renderDepth + 1}
+                        setHeight={this.setSidebarHeight}
+                        fitContentsToBox={this.fitContentsToBox}
+                        noSidebar={true}
+                        treeViewHideTitle={true}
+                        fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-sidebar`}
+                    />
+                </div>
             );
         };
         return (
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 22ca76b2e..21326efaa 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -394,7 +394,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // remove all node type and apply the passed-in one to the selected text
     changeListType = (mapStyle: string) => {
         const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
-        const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle });
+        const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
         if (!this.view || nodeType?.attrs.mapStyle === '') return;
 
         const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
-- 
cgit v1.2.3-70-g09d2


From cfa31b87f1c2a6597ed3cfb6a777126c58ace665 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 2 Aug 2022 16:28:18 -0400
Subject: Adjusted ScriptFields to have a rawScript, and updated ScrptingBoxes
 to create a scriptField even for scripts that don't compile.  Updated
 CurrentUserUtils setup functions for clicks.   Fixed TemplateMenu to work
 again.

---
 src/client/documents/Documents.ts                  |  14 +--
 src/client/util/CurrentUserUtils.ts                |  88 +++++++-------
 src/client/views/DocumentButtonBar.tsx             |  15 ++-
 src/client/views/ScriptBox.tsx                     | 134 +++++++++++----------
 src/client/views/TemplateMenu.tsx                  |  98 ++++++---------
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 src/client/views/nodes/DocumentView.scss           |  23 ++--
 src/client/views/nodes/DocumentView.tsx            |  22 +++-
 src/client/views/nodes/FieldView.tsx               |   5 +-
 src/client/views/nodes/ScriptingBox.tsx            |  85 +++++++------
 src/client/views/nodes/button/FontIconBox.tsx      |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 src/fields/ScriptField.ts                          |  11 +-
 13 files changed, 259 insertions(+), 252 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 51a9283f4..64d26e425 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -264,12 +264,6 @@ export class DocumentOptions {
     baseProto?: boolean; // is this a base prototoype
     dontRegisterView?: boolean;
     lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document.  see PresBox.
-    'onDoubleClick-rawScript'?: string; // onDoubleClick script in raw text form
-    'onChildDoubleClick-rawScript'?: string; // onChildDoubleClick script in raw text form
-    'onChildClick-rawScript'?: string; // on ChildClick script in raw text form
-    'onClick-rawScript'?: string; // onClick script in raw text form
-    'onCheckedClick-rawScript'?: string; // onChecked script in raw text form
-    'onCheckedClick-params'?: List<string>; // parameter list for onChecked treeview functions
     columnHeaders?: List<SchemaHeaderField>; // headers for stacking views
     schemaHeaders?: List<SchemaHeaderField>; // headers for schema view
     clipWidth?: number; // percent transition from before to after in comparisonBox
@@ -1065,7 +1059,7 @@ export namespace Docs {
         }
 
         export function ButtonDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), 'onClick-rawScript': '-script-' });
+            return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
         }
 
         export function SliderDocument(options?: DocumentOptions) {
@@ -1356,7 +1350,11 @@ export namespace DocUtils {
         scripts &&
             Object.keys(scripts).map(key => {
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) {
-                    doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', documentView: Doc.name }, { _readOnly_: true });
+                    doc[key] = ScriptField.MakeScript(
+                        scripts[key],
+                        { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name },
+                        { _readOnly_: true }
+                    );
                 }
             });
         funcs &&
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ce32595d4..f55ed6e72 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -97,6 +97,45 @@ export class CurrentUserUtils {
         return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
     }
 
+    /// Initializes templates for editing click funcs of a document 
+    static setupChildClickEditors(doc: Doc, field = "clickFuncs-child") {
+        const tempClicks = DocCast(doc[field]);
+        const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true};
+        const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}];
+        const reqdClickList = reqdTempOpts.map(opts => {
+            const allOpts = {...reqdClickOpts, ...opts.opts};
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts));
+        });
+
+        const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true};
+        return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
+    }
+
+    /// Initializes templates for editing click funcs of a document 
+    static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") {
+        const tempClicks = DocCast(doc[field]);
+        const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true};
+        const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
+            { opts: { title: "onClick"},             script: "console.log( 'click')"},
+            { opts: { title: "onDoubleClick"},       script: "console.log( 'double click')"},
+            { opts: { title: "onChildClick"},        script: "console.log( 'child click')"},
+            { opts: { title: "onChildDoubleClick"},  script: "console.log( 'child double click')"},
+            { opts: { title: "onCheckedClick"},      script: "console.log( heading, checked, containingTreeView)"},
+        ];
+        const reqdClickList = reqdTempOpts.map(opts => {
+            const allOpts = {...reqdClickOpts, ...opts.opts};
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title);
+        });
+
+        const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, system: true};
+        return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
+    }
+
+
     /// Initializes templates that can be applied to notes
     static setupNoteTemplates(doc: Doc, field="template-notes") {
         const tempNotes = DocCast(doc[field]);
@@ -105,7 +144,7 @@ export class CurrentUserUtils {
             { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
             { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
         const reqdNoteList = reqdTempOpts.map(opts => {
-            const reqdOpts = {...opts, title: "text", system: true};
+            const reqdOpts = {...opts, title: "text", width:200, system: true};
             const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
             return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
         });
@@ -118,10 +157,10 @@ export class CurrentUserUtils {
     static setupDocTemplates(doc: Doc, field="myTemplates") {
         DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"});
         const templates = [
-            DocCast(doc.presElement),
             CurrentUserUtils.setupNoteTemplates(doc),
             CurrentUserUtils.setupClickEditorTemplates(doc)
         ];
+        CurrentUserUtils.setupChildClickEditors(doc)
         const reqdOpts = { title: "template layouts", _xMargin: 0, system: true,  };
         const reqdScripts = {  dropConverter: "convertToButtons(dragData)"  };
         return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts);
@@ -748,51 +787,6 @@ export class CurrentUserUtils {
         DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
         return myImports;
     }
-
-    static setupClickEditorTemplates(doc: Doc) {
-        if (doc["clickFuncs-child"] === undefined) {
-            // to use this function, select it from the context menu of a collection.  then edit the onChildClick script.  Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection.  After that, clicking on any document in the initial collection will open it in the target
-            const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
-                "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ",
-                { thisContainer: Doc.name }), {
-                title: "Click to open in target", _width: 300, _height: 200,
-                targetScriptKey: "onChildClick", system: true
-            });
-
-            const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)",  {}), 
-                { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true });
-
-            doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true });
-        }
-
-        if (doc.clickFuncs === undefined) {
-            const onClick = Docs.Create.ScriptingDocument(undefined, {
-                title: "onClick", "onClick-rawScript": "console.log('click')",
-                isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true
-            }, "onClick");
-            const onChildClick = Docs.Create.ScriptingDocument(undefined, {
-                title: "onChildClick", "onChildClick-rawScript": "console.log('child click')",
-                isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true
-            }, "onChildClick");
-            const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
-                title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
-                isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true
-            }, "onDoubleClick");
-            const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, {
-                title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')",
-                isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true
-            }, "onChildDoubleClick");
-            const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
-                title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)",
-                "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true,
-                isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true
-            }, "onCheckedClick");
-            doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true });
-        }
-
-        return doc.clickFuncs as Doc;
-    }
-
     /// Updates the UserDoc to have all required fields, docs, etc.  No changes should need to be
     /// written to the server if the code hasn't changed.  However, choices need to be made for each Doc/field
     /// whether to revert to "default" values, or to leave them as the user/system last set them.
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index bac51a11d..1d4056759 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -335,8 +335,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         );
     }
     @observable _aliasDown = false;
-    onAliasButtonDown = action((e: React.PointerEvent): void => {
-        this.props.views()[0]?.select(false);
+    onTemplateButton = action((e: React.PointerEvent): void => {
         this._tooltipOpen = false;
         setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
     });
@@ -374,7 +373,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                                 </div>
                             )
                         }>
-                        <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onAliasButtonDown}>
+                        <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
                             {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
                         </div>
                     </Flyout>
@@ -412,15 +411,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
                     </div>
                 ) : null}
-                {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
-                {this.templateButton}
-            </div>
-            /*<div className="documentButtonBar-button">
+                {
+                    Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
+                    /*<div className="documentButtonBar-button">
                 {this.metadataButton}
             </div>
             <div className="documentButtonBar-button">
                 {this.contextButton}
-            </div> */}
+            </div> */
+                }
                 {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index b7ea124b9..416162aeb 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,17 +1,16 @@
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, Opt } from "../../fields/Doc";
-import { ScriptField } from "../../fields/ScriptField";
-import { ScriptCast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
-import { DragManager } from "../util/DragManager";
-import { CompileScript } from "../util/Scripting";
-import { EditableView } from "./EditableView";
-import { DocumentIconContainer } from "./nodes/DocumentIcon";
-import { OverlayView } from "./OverlayView";
-import "./ScriptBox.scss";
-
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, Opt } from '../../fields/Doc';
+import { ScriptField } from '../../fields/ScriptField';
+import { ScriptCast } from '../../fields/Types';
+import { emptyFunction } from '../../Utils';
+import { DragManager } from '../util/DragManager';
+import { CompileScript } from '../util/Scripting';
+import { EditableView } from './EditableView';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { OverlayView } from './OverlayView';
+import './ScriptBox.scss';
 
 export interface ScriptBoxProps {
     onSave: (text: string, onError: (error: string) => void) => void;
@@ -28,53 +27,58 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
 
     constructor(props: ScriptBoxProps) {
         super(props);
-        this._scriptText = props.initialText || "";
+        this._scriptText = props.initialText || '';
     }
 
     @action
     onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
         this._scriptText = e.target.value;
-    }
+    };
 
     @action
     onError = (error: string) => {
-        console.log("ScriptBox: " + error);
-    }
+        console.log('ScriptBox: ' + error);
+    };
 
     overlayDisposer?: () => void;
     onFocus = () => {
         this.overlayDisposer?.();
         this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
-    }
+    };
 
     onBlur = () => {
         this.overlayDisposer?.();
-    }
+    };
 
     render() {
-        let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined;
+        let onFocus: Opt<() => void> = undefined,
+            onBlur: Opt<() => void> = undefined;
         if (this.props.showDocumentIcons) {
             onFocus = this.onFocus;
             onBlur = this.onBlur;
         }
-        const params = <EditableView
-            contents={""}
-            display={"block"}
-            maxHeight={72}
-            height={35}
-            fontSize={28}
-            GetValue={() => ""}
-            SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true}
-        />;
+        const params = <EditableView contents={''} display={'block'} maxHeight={72} height={35} fontSize={28} GetValue={() => ''} SetValue={(value: string) => (this.props.setParams?.(value.split(' ').filter(s => s !== ' ')) ? true : true)} />;
         return (
             <div className="scriptBox-outerDiv">
-                <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
+                <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
                     <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
-                    <div style={{ background: "beige" }} >{params}</div>
+                    <div style={{ background: 'beige' }}>{params}</div>
                 </div>
                 <div className="scriptBox-toolbar">
-                    <button onClick={e => { this.props.onSave(this._scriptText, this.onError); e.stopPropagation(); }}>Save</button>
-                    <button onClick={e => { this.props.onCancel && this.props.onCancel(); e.stopPropagation(); }}>Cancel</button>
+                    <button
+                        onClick={e => {
+                            this.props.onSave(this._scriptText, this.onError);
+                            e.stopPropagation();
+                        }}>
+                        Save
+                    </button>
+                    <button
+                        onClick={e => {
+                            this.props.onCancel && this.props.onCancel();
+                            e.stopPropagation();
+                        }}>
+                        Cancel
+                    </button>
                 </div>
             </div>
         );
@@ -90,35 +94,43 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
         // tslint:disable-next-line: no-unnecessary-callback-wrapper
         const params: string[] = [];
         const setParams = (p: string[]) => params.splice(0, params.length, ...p);
-        const scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => {
-            if (!text) {
-                Doc.GetProto(doc)[fieldKey] = undefined;
-            } else {
-                const script = CompileScript(text, {
-                    params: { this: Doc.name, ...contextParams },
-                    typecheck: false,
-                    editable: true,
-                    transformer: DocumentIconContainer.getTransformer()
-                });
-                if (!script.compiled) {
-                    onError(script.errors.map(error => error.messageText).join("\n"));
-                    return;
-                }
+        const scriptingBox = (
+            <ScriptBox
+                initialText={originalText}
+                setParams={setParams}
+                onCancel={overlayDisposer}
+                onSave={(text, onError) => {
+                    if (!text) {
+                        Doc.GetProto(doc)[fieldKey] = undefined;
+                    } else {
+                        const script = CompileScript(text, {
+                            params: { this: Doc.name, ...contextParams },
+                            typecheck: false,
+                            editable: true,
+                            transformer: DocumentIconContainer.getTransformer(),
+                        });
+                        if (!script.compiled) {
+                            onError(script.errors.map(error => error.messageText).join('\n'));
+                            return;
+                        }
 
-                const div = document.createElement("div");
-                div.style.width = "90";
-                div.style.height = "20";
-                div.style.background = "gray";
-                div.style.position = "absolute";
-                div.style.display = "inline-block";
-                div.style.transform = `translate(${clientX}px, ${clientY}px)`;
-                div.innerHTML = "button";
-                params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY);
+                        const div = document.createElement('div');
+                        div.style.width = '90';
+                        div.style.height = '20';
+                        div.style.background = 'gray';
+                        div.style.position = 'absolute';
+                        div.style.display = 'inline-block';
+                        div.style.transform = `translate(${clientX}px, ${clientY}px)`;
+                        div.innerHTML = 'button';
+                        params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
 
-                Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
-                overlayDisposer();
-            }
-        }} showDocumentIcons />;
+                        Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
+                        overlayDisposer();
+                    }
+                }}
+                showDocumentIcons
+            />
+        );
         overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title });
     }
 }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 156513f47..863829a51 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -55,16 +55,12 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
 
     toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
-        this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout));
+        this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true));
     };
     toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
         this.props.docViews.map(dv => dv.switchViews(false, 'layout'));
     };
 
-    toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
-        this.props.docViews.map(dv => (dv.props.Document._showAudio = e.target.checked));
-    };
-
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: string): void => {
@@ -76,12 +72,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         this._hidden = !this._hidden;
     };
 
-    @undoBatch
-    @action
-    toggleChrome = (): void => {
-        this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => (layout._chromeHidden = !layout._chromeHidden));
-    };
-
     // todo: add brushes to brushMap to save with a style name
     onCustomKeypress = (e: React.KeyboardEvent) => {
         if (e.key === 'Enter') {
@@ -95,13 +85,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
 
-    return100 = () => 100;
+    return100 = () => 300;
     @computed get scriptField() {
-        const script = ScriptField.MakeScript(
-            'docs.map(d => switchView(d, this))',
-            { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name, firstDoc: Doc.name },
-            { docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) }
-        );
+        const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.props.Document) as any });
         return script ? () => script : undefined;
     }
     templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
@@ -113,13 +99,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const firstDoc = this.props.docViews[0].props.Document;
         const templateName = StrCast(firstDoc.layoutKey, 'layout').replace('layout_', '');
         const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data);
-        const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data);
-        const layout = Doc.Layout(firstDoc);
+        const addedTypes = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates?.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
-        templateMenu.push(<OtherToggle key={'audio'} name={'Audio'} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={'default'} name={'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
-        !Doc.noviceMode && templateMenu.push(<OtherToggle key={'chrome'} name={'Chrome'} checked={!layout._chromeHidden} toggle={this.toggleChrome} />);
         addedTypes.concat(noteTypes).map(template => (template.treeViewChecked = this.templateIsUsed(firstDoc, template)));
         this._addedKeys &&
             Array.from(this._addedKeys)
@@ -129,43 +112,42 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             <ul className="template-list" style={{ display: 'block' }}>
                 {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
                 {templateMenu}
-                {Doc.noviceMode ? null : (
-                    <CollectionTreeView
-                        Document={Doc.MyTemplates}
-                        CollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        ContainingCollectionView={undefined}
-                        styleProvider={DefaultStyleProvider}
-                        setHeight={returnFalse}
-                        docViewPath={returnEmptyDoclist}
-                        docFilters={returnEmptyFilter}
-                        docRangeFilters={returnEmptyFilter}
-                        searchFilterDocs={returnEmptyDoclist}
-                        rootSelected={returnFalse}
-                        onCheckedClick={this.scriptField}
-                        onChildClick={this.scriptField}
-                        dropAction={undefined}
-                        isAnyChildContentActive={returnFalse}
-                        isContentActive={returnTrue}
-                        bringToFront={emptyFunction}
-                        focus={emptyFunction}
-                        whenChildContentsActiveChanged={emptyFunction}
-                        ScreenToLocalTransform={Transform.Identity}
-                        isSelected={returnFalse}
-                        pinToPres={emptyFunction}
-                        select={emptyFunction}
-                        renderDepth={1}
-                        addDocTab={returnFalse}
-                        PanelWidth={this.return100}
-                        PanelHeight={this.return100}
-                        treeViewHideHeaderFields={true}
-                        dontRegisterView={true}
-                        fieldKey={'data'}
-                        moveDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        addDocument={returnFalse}
-                    />
-                )}
+                <CollectionTreeView
+                    Document={Doc.MyTemplates}
+                    CollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    ContainingCollectionView={undefined}
+                    styleProvider={DefaultStyleProvider}
+                    setHeight={returnFalse}
+                    docViewPath={returnEmptyDoclist}
+                    docFilters={returnEmptyFilter}
+                    docRangeFilters={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    rootSelected={returnFalse}
+                    onCheckedClick={this.scriptField}
+                    onChildClick={this.scriptField}
+                    dropAction={undefined}
+                    isAnyChildContentActive={returnFalse}
+                    isContentActive={returnTrue}
+                    bringToFront={emptyFunction}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    ScreenToLocalTransform={Transform.Identity}
+                    isSelected={returnFalse}
+                    pinToPres={emptyFunction}
+                    select={emptyFunction}
+                    renderDepth={1}
+                    addDocTab={returnFalse}
+                    PanelWidth={this.return100}
+                    PanelHeight={this.return100}
+                    treeViewHideHeaderFields={true}
+                    treeViewHideTitle={true}
+                    dontRegisterView={true}
+                    fieldKey={'data'}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    addDocument={returnFalse}
+                />
             </ul>
         );
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6850fb23a..a6c367ff7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -296,7 +296,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
                 NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
                 dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
-                dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
+                dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
                 rootSelected={this.rootSelected}
                 showTitle={this.props.childShowTitle}
                 dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 6a1bfa406..c02692bfb 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
 
 .documentView-effectsWrapper {
     border-radius: inherit;
@@ -24,7 +24,7 @@
     width: 100%;
     height: 100%;
     border-radius: inherit;
-    transition: outline .3s linear;
+    transition: outline 0.3s linear;
     cursor: grab;
 
     // background: $white; //overflow: hidden;
@@ -62,7 +62,7 @@
 
     .documentView-audioBackground {
         display: inline-block;
-        width: 10%;
+        width: 25px;
         height: 25;
         position: absolute;
         top: 10px;
@@ -88,7 +88,7 @@
         width: 100%;
         overflow: hidden;
 
-        >.documentView-node {
+        > .documentView-node {
             position: absolute;
         }
     }
@@ -158,7 +158,7 @@
             top: 0;
             width: 100%;
             height: 14;
-            background: rgba(0, 0, 0, .4);
+            background: rgba(0, 0, 0, 0.4);
             text-align: center;
             text-overflow: ellipsis;
             white-space: pre;
@@ -187,19 +187,18 @@
             transition: opacity 0.5s;
         }
     }
-
 }
 
 .documentView-node:hover,
 .documentView-node-topmost:hover {
-    >.documentView-styleWrapper {
-        >.documentView-titleWrapper-hover {
+    > .documentView-styleWrapper {
+        > .documentView-titleWrapper-hover {
             display: inline-block;
         }
     }
 
-    >.documentView-styleWrapper {
-        >.documentView-captionWrapper {
+    > .documentView-styleWrapper {
+        > .documentView-captionWrapper {
             opacity: 1;
         }
     }
@@ -225,6 +224,6 @@
 
     .documentView-node:first-child {
         position: relative;
-        background: "#B59B66"; //$white;
+        background: '#B59B66'; //$white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8847c0c6a..edaa40bad 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -177,6 +177,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
     NativeWidth?: () => number;
     NativeHeight?: () => number;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
     LayoutTemplate?: () => Opt<Doc>;
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
     onClick?: () => ScriptField;
@@ -191,7 +192,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
 export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeWidth: () => number;
     NativeHeight: () => number;
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (ctrlPressed: boolean) => void;
     DocumentView: () => DocumentView;
@@ -716,7 +716,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.Document.followLinkLocation = location;
         } else if (this.Document._isLinkButton && this.onClickHandler) {
             this.Document._isLinkButton = false;
-            this.Document['onClick-rawScript'] = this.dataDoc['onClick-rawScript'] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
+            this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
         }
     };
     @undoBatch
@@ -750,7 +750,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
-    @undoBatch setToggleDetail = () => (this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace('layout_', '')}")`, { documentView: 'any' }));
+    @undoBatch setToggleDetail = () =>
+        (this.Document.onClick = ScriptField.MakeScript(
+            `toggleDetail(documentView, "${StrCast(this.Document.layoutKey)
+                .replace('layout_', '')
+                .replace(/^layout$/, 'detail')}")`,
+            { documentView: 'any' }
+        ));
 
     @undoBatch
     @action
@@ -1538,11 +1544,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         Doc.setNativeView(this.props.Document);
         custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
     };
-    switchViews = action((custom: boolean, view: string, finished?: () => void) => {
+    switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
         this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
         setTimeout(
             action(() => {
-                this.setCustomView(custom, view);
+                if (useExistingLayout && custom && this.rootDoc['layout_' + view]) {
+                    this.rootDoc.layoutKey = 'layout_' + view;
+                } else {
+                    this.setCustomView(custom, view);
+                }
                 this.docView && (this.docView._animateScalingTo = 1); // expand it
                 setTimeout(
                     action(() => {
@@ -1646,7 +1656,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
 
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
     if (dv.Document.layoutKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
-    else dv.switchViews(true, detailLayoutKeySuffix);
+    else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
 });
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5a6c49809..dd2c13391 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -4,10 +4,9 @@ import { observer } from 'mobx-react';
 import { DateField } from '../../../fields/DateField';
 import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
-import { WebField } from '../../../fields/URLField';
-import { DocumentView, DocumentViewSharedProps } from './DocumentView';
 import { ScriptField } from '../../../fields/ScriptField';
-import { RecordingBox } from './RecordingBox';
+import { WebField } from '../../../fields/URLField';
+import { DocumentViewSharedProps } from './DocumentView';
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 05ff40f22..1c9b0bc0e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -6,7 +6,7 @@ import { Doc } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { returnEmptyString } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
@@ -60,6 +60,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     constructor(props: any) {
         super(props);
+        if (!this.compileParams.length) {
+            const params = ScriptCast(this.rootDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
+            if (params) {
+                this.compileParams = Array.from(Object.keys(params))
+                    .filter(p => !p.startsWith('_'))
+                    .map(key => key + ':' + params[key]);
+            }
+        }
     }
 
     // vars included in fields that store parameters types and names and the script itself
@@ -70,30 +78,30 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         return this.compileParams.map(p => p.split(':')[1].trim());
     }
     @computed({ keepAlive: true }) get rawScript() {
-        return StrCast(this.dataDoc[this.props.fieldKey + '-rawScript'], '');
+        return ScriptCast(this.rootDoc[this.fieldKey])?.script.originalScript ?? '';
     }
     @computed({ keepAlive: true }) get functionName() {
-        return StrCast(this.dataDoc[this.props.fieldKey + '-functionName'], '');
+        return StrCast(this.rootDoc[this.props.fieldKey + '-functionName'], '');
     }
     @computed({ keepAlive: true }) get functionDescription() {
-        return StrCast(this.dataDoc[this.props.fieldKey + '-functionDescription'], '');
+        return StrCast(this.rootDoc[this.props.fieldKey + '-functionDescription'], '');
     }
     @computed({ keepAlive: true }) get compileParams() {
-        return Cast(this.dataDoc[this.props.fieldKey + '-params'], listSpec('string'), []);
+        return Cast(this.rootDoc[this.props.fieldKey + '-params'], listSpec('string'), []);
     }
 
     set rawScript(value) {
-        this.dataDoc[this.props.fieldKey + '-rawScript'] = value;
+        Doc.SetInPlace(this.rootDoc, this.props.fieldKey, new ScriptField(undefined, undefined, value), true);
     }
     set functionName(value) {
-        this.dataDoc[this.props.fieldKey + '-functionName'] = value;
+        Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionName', value, true);
     }
     set functionDescription(value) {
-        this.dataDoc[this.props.fieldKey + '-functionDescription'] = value;
+        Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionDescription', value, true);
     }
 
     set compileParams(value) {
-        this.dataDoc[this.props.fieldKey + '-params'] = new List<string>(value);
+        Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-params', new List<string>(value), true);
     }
 
     getValue(result: any, descrip: boolean) {
@@ -107,8 +115,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     componentDidMount() {
-        this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript ?? this.rawScript;
-
+        this.rawText = this.rawScript;
         const observer = new _global.ResizeObserver(
             action((entries: any) => {
                 const area = document.querySelector('textarea');
@@ -171,13 +178,13 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         const params: ScriptParam = {};
         this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
 
-        const result = CompileScript(this.rawScript, {
+        const result = CompileScript(this.rawText, {
             editable: true,
             transformer: DocumentIconContainer.getTransformer(),
             params,
             typecheck: false,
         });
-        this.dataDoc[this.fieldKey] = result.compiled ? new ScriptField(result) : undefined;
+        Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : new ScriptField(undefined, undefined, this.rawText), true);
         this.onError(result.compiled ? undefined : result.errors);
         return result.compiled;
     };
@@ -187,9 +194,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     onRun = () => {
         if (this.onCompile()) {
             const bindings: { [name: string]: any } = {};
-            this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key]));
+            this.paramsNames.forEach(key => (bindings[key] = this.rootDoc[key]));
             // binds vars so user doesnt have to refer to everything as self.<var>
-            ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError);
+            ScriptCast(this.rootDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError);
         }
     };
 
@@ -257,14 +264,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     // sets field of the corresponding field key (param name) to be dropped document
     @action
     onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
-        this.dataDoc[fieldKey] = de.complete.docDragData?.droppedDocuments[0];
+        Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true);
         e.stopPropagation();
     };
 
     // deletes a param from all areas in which it is stored
     @action
     onDelete = (num: number) => {
-        this.dataDoc[this.paramsNames[num]] = undefined;
+        Doc.SetInPlace(this.rootDoc, this.paramsNames[num], undefined, true);
         this.compileParams.splice(num, 1);
         return true;
     };
@@ -274,7 +281,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     viewChanged = (e: React.ChangeEvent, name: string) => {
         //@ts-ignore
         const val = e.target.selectedOptions[0].value;
-        this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
+        Doc.SetInPlace(this.rootDoc, name, val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true', true);
     };
 
     // creates a copy of the script document
@@ -330,8 +337,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                     maxHeight={72}
                     height={35}
                     fontSize={14}
-                    contents={this.dataDoc[parameter]?.title ?? 'undefined'}
-                    GetValue={() => this.dataDoc[parameter]?.title ?? 'undefined'}
+                    contents={StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
+                    GetValue={() => StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
                     SetValue={action((value: string) => {
                         const script = CompileScript(value, {
                             addReturn: true,
@@ -341,7 +348,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                         const results = script.compiled && script.run();
                         if (results && results.success) {
                             this._errorMessage = '';
-                            this.dataDoc[parameter] = results.result;
+                            Doc.SetInPlace(this.rootDoc, parameter, results.result, true);
                             return true;
                         }
                         this._errorMessage = 'invalid document';
@@ -354,7 +361,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     // rendering when a string's value can be set in applied UI
     renderBasicType(parameter: string, isNum: boolean) {
-        const strVal = isNum ? NumCast(this.dataDoc[parameter]).toString() : StrCast(this.dataDoc[parameter]);
+        const strVal = isNum ? NumCast(this.rootDoc[parameter]).toString() : StrCast(this.rootDoc[parameter]);
         return (
             <div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
                 <EditableView
@@ -368,7 +375,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                         const setValue = isNum ? parseInt(value) : value;
                         if (setValue !== undefined && setValue !== ' ') {
                             this._errorMessage = '';
-                            this.dataDoc[parameter] = setValue;
+                            Doc.SetInPlace(this.rootDoc, parameter, setValue, true);
                             return true;
                         }
                         this._errorMessage = 'invalid input';
@@ -389,7 +396,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                             className="scriptingBox-viewPicker"
                             onPointerDown={e => e.stopPropagation()}
                             onChange={e => this.viewChanged(e, parameter)}
-                            value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
+                            value={typeof this.rootDoc[parameter] === 'string' ? 'S' + StrCast(this.rootDoc[parameter]) : typeof this.rootDoc[parameter] === 'number' ? 'N' + NumCast(this.rootDoc[parameter]) : 'B' + BoolCast(this.rootDoc[parameter])}>
                             {types.map(type => (
                                 <option className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
                                     {' '}
@@ -442,7 +449,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     handleFunc(pos: number) {
-        const scriptString = this.rawScript.slice(0, pos - 2);
+        const scriptString = this.rawText.slice(0, pos - 2);
         this._currWord = scriptString.split(' ')[scriptString.split(' ').length - 1];
         this._suggestions = [StrCast(this._scriptingParams[this._currWord])];
         return this._suggestions;
@@ -474,7 +481,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     }
 
     getSuggestedParams(pos: number) {
-        const firstScript = this.rawScript.slice(0, pos);
+        const firstScript = this.rawText.slice(0, pos);
         const indexP = firstScript.lastIndexOf('.');
         const indexS = firstScript.lastIndexOf(' ');
         const func = firstScript.slice((indexP > indexS ? indexP : indexS) + 1, firstScript.length + 1);
@@ -494,8 +501,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     keyHandler(e: any, pos: number) {
+        e.stopPropagation();
         if (this._lastChar === 'Enter') {
-            this.rawScript = this.rawScript + ' ';
+            this.rawText = this.rawText + ' ';
         }
         if (e.key === '(') {
             this.suggestionPos();
@@ -504,7 +512,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
             this._scriptSuggestedParams = this.getSuggestedParams(pos);
 
             if (this._scriptParamsText !== undefined && this._scriptParamsText.length > 0) {
-                if (this.rawScript[pos - 2] !== '(') {
+                if (this.rawText[pos - 2] !== '(') {
                     this._paramSuggestion = true;
                 }
             }
@@ -515,22 +523,22 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                 if (this._lastChar === '(') {
                     this._paramSuggestion = false;
                 } else if (this._lastChar === ')') {
-                    if (this.rawScript.slice(0, this.rawScript.length - 1).split('(').length - 1 > this.rawScript.slice(0, this.rawScript.length - 1).split(')').length - 1) {
+                    if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
                         if (this._scriptParamsText.length > 0) {
                             this._paramSuggestion = true;
                         }
                     }
                 }
-            } else if (this.rawScript.split('(').length - 1 <= this.rawScript.split(')').length - 1) {
+            } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
                 this._paramSuggestion = false;
             }
         }
-        this._lastChar = e.key === 'Backspace' ? this.rawScript[this.rawScript.length - 2] : e.key;
+        this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
 
         if (this._paramSuggestion) {
             const parameters = this._scriptParamsText.split(',');
-            const index = this.rawScript.lastIndexOf('(');
-            const enteredParams = this.rawScript.slice(index, this.rawScript.length);
+            const index = this.rawText.lastIndexOf('(');
+            const enteredParams = this.rawText.slice(index, this.rawText.length);
             const splitEntered = enteredParams.split(',');
             const numEntered = splitEntered.length;
 
@@ -564,15 +572,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     handlePosChange(number: any) {
         this._caretPos = number;
         if (this._caretPos === 0) {
-            this.rawScript = ' ' + this.rawScript;
+            this.rawText = ' ' + this.rawText;
         } else if (this._spaced) {
             this._spaced = false;
-            if (this.rawScript[this._caretPos - 1] === ' ') {
-                this.rawScript = this.rawScript.slice(0, this._caretPos - 1) + this.rawScript.slice(this._caretPos, this.rawScript.length);
+            if (this.rawText[this._caretPos - 1] === ' ') {
+                this.rawText = this.rawText.slice(0, this._caretPos - 1) + this.rawText.slice(this._caretPos, this.rawText.length);
             }
         }
     }
 
+    @observable rawText: string = '';
     @computed({ keepAlive: true }) get renderScriptingBox() {
         TraceMobx();
         return (
@@ -583,8 +592,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
                     placeholder="write your script here"
                     onFocus={this.onFocus}
                     onBlur={() => this._overlayDisposer?.()}
-                    onChange={(e: any) => (this.rawScript = e.target.value)}
-                    value={this.rawScript}
+                    onChange={action((e: any) => (this.rawText = e.target.value))}
+                    value={this.rawText}
                     movePopupAsYouType={true}
                     loadingComponent={() => <span>Loading</span>}
                     trigger={{
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 5e279f984..78ef85ec2 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -579,11 +579,11 @@ ScriptingGlobals.add(function setView(view: string) {
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Docs().lastElement();
+    const selected = SelectionManager.Views().lastElement();
     if (checkResult) {
-        return selected?._backgroundColor ?? 'transparent';
+        return selected?.props.Document._backgroundColor ?? 'transparent';
     }
-    if (selected) selected._backgroundColor = color;
+    if (selected) selected.props.Document._backgroundColor = color;
 });
 
 // toggle: Set overlay status of selected document
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index eb87d11a4..b8ee89ef2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -398,7 +398,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     updateTitle = () => {
-        const title = StrCast(this.dataDoc.title);
+        const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
             !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith('-') || title.startsWith('@')) &&
@@ -1000,7 +1000,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             // set the document height when one of the component heights changes and autoHeight is on
             () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
             ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
-                autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)));
+                const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
+                autoHeight && newHeight && this.props.setHeight?.(newHeight);
             },
             { fireImmediately: true }
         );
@@ -1768,7 +1769,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+                    //@ts-ignore
                     <ComponentTag
                         {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                         NativeWidth={returnZero}
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 2b4b1ef4c..68fb45987 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -81,6 +81,8 @@ async function deserializeScript(script: ScriptField) {
 @scriptingGlobal
 @Deserializable('script', deserializeScript)
 export class ScriptField extends ObjectField {
+    @serializable
+    readonly rawscript: string | undefined;
     @serializable(object(scriptSchema))
     readonly script: CompiledScript;
     @serializable(object(scriptSchema))
@@ -94,15 +96,16 @@ export class ScriptField extends ObjectField {
         return this._scriptFieldCache.get(field);
     }
 
-    constructor(script: CompiledScript, setterscript?: CompiledScript) {
+    constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) {
         super();
 
         const captured = script?.options.capturedVariables;
         if (captured) {
             this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString())));
         }
+        this.rawscript = rawscript;
         this.setterscript = setterscript;
-        this.script = script;
+        this.script = script ?? (CompileScript('false') as CompiledScript);
     }
 
     //     init(callback: (res: Field) => any) {
@@ -127,7 +130,7 @@ export class ScriptField extends ObjectField {
     //     }
 
     [Copy](): ObjectField {
-        return new ScriptField(this.script, this.setterscript);
+        return new ScriptField(this.script, this.setterscript, this.rawscript);
     }
     toString() {
         return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
@@ -175,7 +178,7 @@ export class ComputedField extends ScriptField {
     _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
 
     [Copy](): ObjectField {
-        return new ComputedField(this.script, this.setterscript);
+        return new ComputedField(this.script, this.setterscript, this.rawscript);
     }
 
     public static MakeScript(script: string, params: object = {}) {
-- 
cgit v1.2.3-70-g09d2


From 55bac585fa0b8d6c3f513ccecb22456d1d361040 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Aug 2022 13:33:38 -0400
Subject: fixes for dragging notes so that they highlight properly and go to
 the right place when embedded in freeform views.

---
 src/client/documents/Documents.ts                  |   9 +-
 src/client/util/CurrentUserUtils.ts                |   3 +-
 src/client/util/DragManager.ts                     |  12 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/StyleProvider.tsx                 |   8 +-
 .../views/collections/CollectionNoteTakingView.tsx |  91 +++---
 .../collections/CollectionNoteTakingViewColumn.tsx | 358 +++++++++++----------
 .../CollectionNoteTakingViewDivider.tsx            | 112 +++----
 .../views/collections/CollectionStackingView.tsx   |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  30 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   6 +-
 11 files changed, 332 insertions(+), 303 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 32d7152fd..9d00664ad 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1036,7 +1036,14 @@ export namespace Docs {
         }
 
         export function NoteTakingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId);
+            return InstanceFromProto(
+                Prototypes.get(DocumentType.COL),
+                new List(documents),
+                { columnHeaders: new List<SchemaHeaderField>([new SchemaHeaderField('Untitled')]), ...options, _viewType: CollectionViewType.NoteTaking },
+                id,
+                undefined,
+                protoId
+            );
         }
 
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 28dc44c25..7856c913b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -135,7 +135,6 @@ export class CurrentUserUtils {
         return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
     }
 
-
     /// Initializes templates that can be applied to notes
     static setupNoteTemplates(doc: Doc, field="template-notes") {
         const tempNotes = DocCast(doc[field]);
@@ -255,6 +254,7 @@ export class CurrentUserUtils {
                           creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _autoHeight: true }},
+            {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200 }},
             {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100 }},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }},
             {key: "Webpage",    creator: opts => Docs.Create.WebDocument("",opts),          opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
@@ -280,6 +280,7 @@ export class CurrentUserUtils {
         
         return [
             { toolTip: "Tap or drag to create a note",            title: "Note", icon: "sticky-note",   dragFactory: doc.emptyNote as Doc, },
+            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "folder",       dragFactory: doc.emptyNoteboard as Doc, },
             { toolTip: "Tap or drag to create a collection",      title: "Col", icon: "folder",         dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab),  scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, },
             { toolTip: "Tap or drag to create an equation",       title: "Math", icon: "calculator",    dragFactory: doc.emptyEquation as Doc, },
             { toolTip: "Tap or drag to create a webpage",         title: "Web", icon: "globe-asia",     dragFactory: doc.emptyWebpage as Doc, },
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 3a1bb1673..f4987cf34 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,4 +1,4 @@
-import { action } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { DateField } from '../../fields/DateField';
 import { Doc, Field, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
@@ -320,7 +320,7 @@ export namespace DragManager {
             y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()),
         };
     }
-    export let docsBeingDragged: Doc[] = [];
+    export let docsBeingDragged: Doc[] = observable([] as Doc[]);
     export let CanEmbed = false;
     export let DocDragData: DocumentDragData | undefined;
     export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
@@ -349,13 +349,13 @@ export namespace DragManager {
             xs: number[] = [],
             ys: number[] = [];
 
-        docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const elesCont = {
             left: Number.MAX_SAFE_INTEGER,
             right: Number.MIN_SAFE_INTEGER,
             top: Number.MAX_SAFE_INTEGER,
             bottom: Number.MIN_SAFE_INTEGER,
         };
+        const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const dragElements = eles.map(ele => {
             if (!ele.parentNode) dragDiv.appendChild(ele);
             const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement);
@@ -405,7 +405,7 @@ export namespace DragManager {
             });
             dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`;
 
-            if (docsBeingDragged.length) {
+            if (docsToDrag.length) {
                 const pdfBox = dragElement.getElementsByTagName('canvas');
                 const pdfBoxSrc = ele.getElementsByTagName('canvas');
                 Array.from(pdfBox)
@@ -429,6 +429,8 @@ export namespace DragManager {
             return dragElement;
         });
 
+        runInAction(() => docsBeingDragged.push(...docsToDrag));
+
         const hideDragShowOriginalElements = (hide: boolean) => {
             dragLabel.style.display = hide ? '' : 'none';
             !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
@@ -456,7 +458,7 @@ export namespace DragManager {
             SnappingManager.SetIsDragging(false);
             SnappingManager.clearSnapLines();
             batch.end();
-            docsBeingDragged = [];
+            docsBeingDragged.length = 0;
         });
         var startWindowDragTimer: any;
         const moveHandler = (e: PointerEvent) => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c53e61699..0db9eab69 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -351,7 +351,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
+        DragManager.docsBeingDragged.push(...SelectionManager.Views().map(dv => dv.rootDoc));
         this._inkDragDocs = DragManager.docsBeingDragged
             .filter(doc => doc.type === DocumentType.INK)
             .map(doc => {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 692f7b98e..a83163eb0 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -110,8 +110,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined;
         case StyleProp.WidgetColor:
             return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? 'lightgrey' : 'dimgrey';
-            // return doc = dragManager.dragDocument ? props.dragEffects.opacity??CastofOpacityonline94 : Cast())
-            // same idea for background Color
+        // return doc = dragManager.dragDocument ? props.dragEffects.opacity??CastofOpacityonline94 : Cast())
+        // same idea for background Color
         case StyleProp.Opacity:
             return Cast(doc?._opacity, 'number', Cast(doc?.opacity, 'number', null));
         case StyleProp.HideLinkButton:
@@ -156,7 +156,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.JitterRotation:
             return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
         case StyleProp.HeaderMargin:
-            return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) || doc?.type === DocumentType.LABEL) &&
+            return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
+                (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) ||
+                doc?.type === DocumentType.LABEL) &&
                 showTitle() &&
                 !StrCast(doc?.showTitle).includes(':hover')
                 ? 15
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index f442559fb..1854a4213 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,6 @@
 import React = require('react');
 import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
@@ -21,10 +21,12 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import './CollectionNoteTakingView.scss';
 import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
-import CollectionNoteTakingViewDivider from './CollectionNoteTakingViewDivider';
+import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
 import { CollectionSubView } from './CollectionSubView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
@@ -40,7 +42,6 @@ export type collectionNoteTakingViewProps = {
 
 @observer
 export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() {
-    _pivotFieldDisposer?: IReactionDisposer;
     _autoHeightDisposer?: IReactionDisposer;
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
@@ -54,10 +55,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
     }
     @computed get columnHeaders() {
-        return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null);
+        return Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null);
     }
-    @computed get pivotField() {
-        return 'Col';
+    @computed get notetakingCategoryField() {
+        return 'noteTakingCategory';
     }
     @computed get filteredChildren() {
         return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
@@ -90,7 +91,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
     constructor(props: any) {
         super(props);
         if (this.columnHeaders === undefined) {
-            this.layoutDoc._columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
+            this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
             this.columnStartXCoords = [0];
             // add all of the docs that have not been added to a column to this new column
         } else {
@@ -126,18 +127,16 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         // filter out the currently dragged docs from the child docs, since we will insert them later
         if (rowCol.length && DragManager.docsBeingDragged.length) {
             const docIdsToRemove = new Set();
-            DragManager.docsBeingDragged.forEach(d => {
-                docIdsToRemove.add(d[Id]);
-            });
+            DragManager.docsBeingDragged.forEach(d => docIdsToRemove.add(d[Id]));
             docs = docs.filter(d => !docIdsToRemove.has(d[Id]));
         }
 
         // this will sort the docs into the correct columns (minus the ones you're currently dragging)
         docs.map(d => {
-            if (!d[this.pivotField]) {
-                d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column`;
+            if (!d[this.notetakingCategoryField]) {
+                d[this.notetakingCategoryField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column`;
             }
-            const sectionValue = d[this.pivotField] as object;
+            const sectionValue = d[this.notetakingCategoryField] as object;
 
             // look for if header exists already
             const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
@@ -156,14 +155,15 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         return sections;
     }
 
+    removeDocDragHighlight = () => {
+        setTimeout(
+            action(() => (this.docsDraggedRowCol.length = 0)),
+            100
+        );
+    };
     componentDidMount() {
         super.componentDidMount?.();
-        // reset section headers when a new filter is inputted
-        this._pivotFieldDisposer = reaction(
-            () => this.pivotField,
-            () => (this.layoutDoc._columnHeaders = new List())
-        );
-
+        document.addEventListener('pointerup', this.removeDocDragHighlight, true);
         this._autoHeightDisposer = reaction(
             () => this.layoutDoc._autoHeight,
             autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
@@ -171,8 +171,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
     }
 
     componentWillUnmount() {
+        document.removeEventListener('pointerup', this.removeDocDragHighlight, true);
         super.componentWillUnmount();
-        this._pivotFieldDisposer?.();
         this._autoHeightDisposer?.();
     }
 
@@ -227,6 +227,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
     };
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+        if (property === StyleProp.BoxShadow && doc && DragManager.docsBeingDragged.includes(doc)) {
+            return `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}`;
+        }
         if (property === StyleProp.Opacity && doc) {
             if (this.props.childOpacity) {
                 return this.props.childOpacity();
@@ -258,9 +261,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
                 docViewPath={this.props.docViewPath}
                 fitWidth={this.props.childFitWidth}
                 isContentActive={emptyFunction}
-                originalBackgroundColor={StrCast(doc.backgroundColor)}
+                onKey={this.onKeyDown}
                 //TODO: change this from a prop to a parameter passed into a function
-                isNoteTakingView={true}
+                dontHideOnDrag={true}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this.props.childLayoutTemplate}
                 LayoutTemplateString={this.props.childLayoutString}
@@ -307,7 +310,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
     // how to get the width of a document. Currently returns the width of the column (minus margins)
     // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
     getDocWidth(d: Doc) {
-        const heading = d[this.pivotField] as object;
+        const heading = d[this.notetakingCategoryField] as object;
         const castedSectionValue = heading.toString();
         const existingHeader = this.columnHeaders.find(sh => sh.heading === castedSectionValue);
         const colStartXCoords = this.columnStartXCoords;
@@ -364,20 +367,14 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         if (DragManager.docsBeingDragged.length && this.childDocList) {
             // get the current docs for the column based on the mouse's x coordinate
             // will use again later, which is why we're saving as local
-            const xCoord = e.clientX - 2 * this.gridGap;
+            const xCoord = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0] - 2 * this.gridGap;
             const colDocs = this.getDocsFromXCoord(xCoord);
             // get the index for where you need to insert the doc you are currently dragging
-            const clientY = e.clientY;
+            const clientY = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[1];
             let dropInd = -1;
-            // unsure whether we still want this dropAfter field
-            // let dropAfter = 0;
-            // manually set to 140, because not sure how to get exact value
-            let pos0 = 140;
+            let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
             colDocs.forEach((doc, i) => {
-                const noteTakingDocTransform = () => this.getDocTransform(doc);
-                let pos1 = noteTakingDocTransform()
-                    .inverse()
-                    .transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap)[1];
+                let pos1 = this.getDocHeight(doc) + 2 * this.gridGap;
                 pos1 += pos0;
                 // updating drop position based on y coordinates
                 const yCoordInBetween = clientY > pos0 && (clientY < pos1 || i == colDocs.length - 1);
@@ -393,10 +390,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
             // we alter the pivot fields of the docs in case they are moved to a new column.
             const colIndex = this.getColumnFromXCoord(xCoord);
             const colHeader = StrCast(this.columnHeaders[colIndex].heading);
-            DragManager.docsBeingDragged.forEach(d => (d[this.pivotField] = colHeader));
+            DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader));
             // used to notify sections to re-render
-            // console.log([dropInd, this.getColumnFromXCoord(xCoord)])
-            this.docsDraggedRowCol = [dropInd, this.getColumnFromXCoord(xCoord)];
+            this.docsDraggedRowCol.length = 0;
+            this.docsDraggedRowCol.push(dropInd, this.getColumnFromXCoord(xCoord));
         }
     };
 
@@ -425,7 +422,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         if (docs) {
             docs.map(d => {
                 if (d instanceof Promise) return;
-                const sectionValue = d[this.pivotField] as object;
+                const sectionValue = d[this.notetakingCategoryField] as object;
                 if (sectionValue.toString() == colHeader) {
                     docsMatchingHeader.push(d);
                 }
@@ -434,13 +431,24 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
         return docsMatchingHeader;
     };
 
+    @undoBatch
+    @action
+    onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+        const docView = fieldProps.DocumentView?.();
+        if (docView && (e.ctrlKey || docView.rootDoc._singleLine) && ['Enter'].includes(e.key)) {
+            e.stopPropagation?.();
+            const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+            Doc.GetProto(newDoc).text = undefined;
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            return this.addDocument?.(newDoc);
+        }
+    };
+
     @undoBatch
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
             if (super.onInternalDrop(e, de)) {
-                DragManager.docsBeingDragged = [];
-                // this.docsDraggedRowCol = []
                 // filter out the currently dragged docs from the child docs, since we will insert them later
                 const rowCol = this.docsDraggedRowCol;
                 const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
@@ -456,10 +464,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
                     if (rowCol[0] <= 0) {
                         docs.splice(0, 0, ...newDocs);
                     } else {
-                        const colDocs = this.getDocsFromXCoord(de.x);
+                        const colDocs = this.getDocsFromXCoord(this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]);
                         const previousDoc = colDocs[rowCol[0] - 1];
                         const previousDocIndex = docs.indexOf(previousDoc);
-                        console.log(`docs: ${previousDocIndex}`);
                         docs.splice(previousDocIndex + 1, 0, ...newDocs);
                     }
                 }
@@ -561,7 +568,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
                 renderChildren={this.children}
                 numGroupColumns={this.numGroupColumns}
                 gridGap={this.gridGap}
-                pivotField={this.pivotField}
+                pivotField={this.notetakingCategoryField}
                 columnStartXCoords={this.columnStartXCoords}
                 maxColWidth={this.maxColWdith}
                 PanelWidth={this.PanelWidth}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index bdcb9c399..55d032498 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,27 +1,27 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { RichTextField } from "../../../fields/RichTextField";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../fields/ScriptField";
-import { ImageField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnEmptyString, setupMoveUpEvents } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DragManager } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { EditableView } from "../EditableView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
-import "./CollectionNoteTakingView.scss";
-const higflyout = require("@hig/flyout");
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { RichTextField } from '../../../fields/RichTextField';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { ScriptField } from '../../../fields/ScriptField';
+import { ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { EditableView } from '../EditableView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import './CollectionNoteTakingView.scss';
+const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
@@ -39,7 +39,7 @@ interface CSVFieldColumnProps {
     // columnWidth: number;
     numGroupColumns: number;
     gridGap: number;
-    type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
+    type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
     headings: () => object[];
     renderChildren: (docs: Doc[]) => JSX.Element[];
     addDocument: (doc: Doc | Doc[]) => boolean;
@@ -48,37 +48,37 @@ interface CSVFieldColumnProps {
     observeHeight: (myref: any) => void;
     unobserveHeight: (myref: any) => void;
     editableViewProps: any;
-    resizeColumns: (n: number) => void
-    columnStartXCoords: number[]
-    PanelWidth: number
-    maxColWidth: number
+    resizeColumns: (n: number) => void;
+    columnStartXCoords: number[];
+    PanelWidth: number;
+    maxColWidth: number;
     // docsByColumnHeader: Map<string, Doc[]>
     // setDocsForColHeader: (key: string, docs: Doc[]) => void
 }
 
 @observer
 export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColumnProps> {
-    @observable private _background = "inherit";
+    @observable private _background = 'inherit';
 
     @computed get columnWidth() {
-      // base cases
-      if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
-        return this.props.maxColWidth
-      }
-      const i = this.props.columnHeaders.indexOf(this.props.headingObject)
-      if (i < 0) {
-        return this.props.maxColWidth
-      }
-      const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i+1]
-      // TODO make the math work here. 35 is half of 70, which is the current width of the divider
-      return endColValue - this.props.columnStartXCoords[i] - 30
+        // base cases
+        if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
+            return this.props.maxColWidth;
+        }
+        const i = this.props.columnHeaders.indexOf(this.props.headingObject);
+        if (i < 0) {
+            return this.props.maxColWidth;
+        }
+        const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i + 1];
+        // TODO make the math work here. 35 is half of 70, which is the current width of the divider
+        return endColValue - this.props.columnStartXCoords[i] - 30;
     }
 
     private dropDisposer?: DragManager.DragDropDisposer;
     private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
 
     @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
-    @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+    @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
     _ele: HTMLElement | null = null;
 
     // This is likely similar to what we will be doing. Why do we need to make these refs?
@@ -90,7 +90,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
             this.props.observeHeight(ele);
             this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
         }
-    }
+    };
 
     componentWillUnmount() {
         this.props.unobserveHeight(this._ele);
@@ -105,10 +105,10 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
     getValue = (value: string): any => {
         const parsed = parseInt(value);
         if (!isNaN(parsed)) return parsed;
-        if (value.toLowerCase().indexOf("true") > -1) return true;
-        if (value.toLowerCase().indexOf("false") > -1) return false;
+        if (value.toLowerCase().indexOf('true') > -1) return true;
+        if (value.toLowerCase().indexOf('false') > -1) return false;
         return value;
-    }
+    };
 
     @action
     headingChanged = (value: string, shiftDown?: boolean) => {
@@ -117,7 +117,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
             if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this.props.docList.forEach(d => d[this.props.pivotField] = castedValue);
+            this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
             if (this.props.headingObject) {
                 this.props.headingObject.setHeading(castedValue.toString());
                 this._heading = this.props.headingObject.heading;
@@ -125,11 +125,11 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
             return true;
         }
         return false;
-    }
+    };
 
-    @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
-    @action pointerLeave = () => this._background = "inherit";
-    textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
+    @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
+    @action pointerLeave = () => (this._background = 'inherit');
+    textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
 
     @action
     addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -139,46 +139,46 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         const colValue = this.getValue(this.props.heading);
         newDoc[key] = colValue;
         FormattedTextBox.SelectOnLoad = newDoc[Id];
-        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
+        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
         return this.props.addDocument?.(newDoc) || false;
-    }
+    };
 
     @action
     deleteColumn = () => {
         if (!this.props.columnHeaders) {
-          return
+            return;
         }
         if (this.props.headingObject) {
-          const index = this.props.columnHeaders.indexOf(this.props.headingObject);
-          const newIndex = index == 0 ? 1 : index - 1
-          const newHeader = this.props.columnHeaders[newIndex];
-          this.props.docList.forEach(d => d[this.props.pivotField] = newHeader.heading.toString())
-          this.props.columnHeaders.splice(index, 1);
-          this.props.resizeColumns(this.props.columnHeaders.length)
+            const index = this.props.columnHeaders.indexOf(this.props.headingObject);
+            const newIndex = index == 0 ? 1 : index - 1;
+            const newHeader = this.props.columnHeaders[newIndex];
+            this.props.docList.forEach(d => (d[this.props.pivotField] = newHeader.heading.toString()));
+            this.props.columnHeaders.splice(index, 1);
+            this.props.resizeColumns(this.props.columnHeaders.length);
         }
-    }
+    };
 
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
 
-    //TODO: I think this is where I'm supposed to edit stuff 
+    //TODO: I think this is where I'm supposed to edit stuff
     startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
-        console.log('in startDrag')
+        console.log('in startDrag');
         // is MakeAlias a way to make a copy of a doc without rendering it?
         const alias = Doc.MakeAlias(this.props.Document);
         // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1);
         alias._width = this.columnWidth;
         alias._pivotField = undefined;
         let value = this.getValue(this._heading);
-        value = typeof value === "string" ? `"${value}"` : value;
+        value = typeof value === 'string' ? `"${value}"` : value;
         alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
         if (alias.viewSpecScript) {
-            const options = {hideSource: false}
+            const options = { hideSource: false };
             DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options);
-            console.log('in startDrag')
+            console.log('in startDrag');
             return true;
         }
         return false;
-    }
+    };
 
     menuCallback = (x: number, y: number) => {
         ContextMenu.Instance.clearItems();
@@ -187,44 +187,62 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         const dataDoc = this.props.DataDoc || this.props.Document;
         const pivotValue = this.getValue(this.props.heading);
 
-        DocUtils.addDocumentCreatorMenuItems((doc) => {
-            const key = this.props.pivotField;
-            doc[key] = this.getValue(this.props.heading);
-            FormattedTextBox.SelectOnLoad = doc[Id];
-            return this.props.addDocument?.(doc);
-        }, this.props.addDocument, x, y, true, this.props.pivotField, pivotValue);
+        DocUtils.addDocumentCreatorMenuItems(
+            doc => {
+                const key = this.props.pivotField;
+                doc[key] = this.getValue(this.props.heading);
+                FormattedTextBox.SelectOnLoad = doc[Id];
+                return this.props.addDocument?.(doc);
+            },
+            this.props.addDocument,
+            x,
+            y,
+            true,
+            this.props.pivotField,
+            pivotValue
+        );
 
-        Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
-            docItems.push({
-                description: ":" + fieldKey, event: () => {
-                    const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
-                    if (created) {
-                        if (this.props.Document.isTemplateDoc) {
-                            Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+        Array.from(Object.keys(Doc.GetProto(dataDoc)))
+            .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
+            .map(fieldKey =>
+                docItems.push({
+                    description: ':' + fieldKey,
+                    event: () => {
+                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+                        if (created) {
+                            if (this.props.Document.isTemplateDoc) {
+                                Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+                            }
+                            return this.props.addDocument?.(created);
                         }
-                        return this.props.addDocument?.(created);
-                    }
-                }, icon: "compress-arrows-alt"
-            }));
-        Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey =>
-            docItems.push({
-                description: ":" + fieldKey, event: () => {
-                    const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
-                    if (created) {
-                        const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
-                        if (container.isTemplateDoc) {
-                            Doc.MakeMetadataFieldTemplate(created, container);
-                            return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+                    },
+                    icon: 'compress-arrows-alt',
+                })
+            );
+        Array.from(Object.keys(Doc.GetProto(dataDoc)))
+            .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
+            .map(fieldKey =>
+                docItems.push({
+                    description: ':' + fieldKey,
+                    event: () => {
+                        const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+                        if (created) {
+                            const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+                            if (container.isTemplateDoc) {
+                                Doc.MakeMetadataFieldTemplate(created, container);
+                                return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+                            }
+                            return this.props.addDocument?.(created) || false;
                         }
-                        return this.props.addDocument?.(created) || false;
-                    }
-                }, icon: "compress-arrows-alt"
-            }));
-        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
-        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
-        ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
-            Doc.GetProto(this.props.Document)[name] = "";
-            const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+                    },
+                    icon: 'compress-arrows-alt',
+                })
+            );
+        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
+        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+        ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+            Doc.GetProto(this.props.Document)[name] = '';
+            const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
             if (created) {
                 if (this.props.Document.isTemplateDoc) {
                     Doc.MakeMetadataFieldTemplate(created, this.props.Document);
@@ -233,98 +251,100 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
             }
         });
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
-    }
+    };
 
     @computed get innards() {
         TraceMobx();
         const key = this.props.pivotField;
         const heading = this._heading;
         const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
-        const evContents = heading ? heading : "25";
-        const headingView = this.props.headingObject ?
-            <div key={heading} className="collectionNoteTakingView-sectionHeader" ref={this._headerRef}
+        const evContents = heading ? heading : '25';
+        const headingView = this.props.headingObject ? (
+            <div
+                key={heading}
+                className="collectionNoteTakingView-sectionHeader"
+                ref={this._headerRef}
                 style={{
                     marginTop: 2 * this.props.yMargin,
                     // width: (this.props.columnWidth) /
                     //     ((uniqueHeadings.length) || 1)
-                    width: this.columnWidth - 20
+                    width: this.columnWidth - 20,
                 }}>
-                <div className="collectionNoteTakingView-sectionHeader-subCont" onPointerDown={this.headerDown}
-                    title={evContents === `No Value` ?
-                        `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
-                    style={{ background: evContents !== `No Value` ? this._color : "inherit" }}>
-                    <EditableView
-                        GetValue={() => evContents}
-                        SetValue={this.headingChanged}
-                        contents={evContents}
-                        oneLine={true}
-                    />
+                <div
+                    className="collectionNoteTakingView-sectionHeader-subCont"
+                    onPointerDown={this.headerDown}
+                    title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
+                    style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
+                    <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
                 </div>
-            </div> : (null);
+            </div>
+        ) : null;
         // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
         const templatecols = `${this.columnWidth}px `;
         const type = this.props.Document.type;
-        return <>
-            {headingView}
-            {<div>
-              <div key={`${heading}-stack`} className={`collectionNoteTakingView-Nodes`}
-                  style={{
-                      padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
-                      margin: "auto",
-                      width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
-                      height: 'max-content',
-                      position: "relative",
-                      gridGap: this.props.gridGap,
-                      gridTemplateColumns: templatecols,
-                      gridAutoRows: "0px"
-                  }}>
-                  {this.props.renderChildren(this.props.docList)}
-              </div>
+        return (
+            <>
+                {headingView}
+                {
+                    <div>
+                        <div
+                            key={`${heading}-stack`}
+                            className={`collectionNoteTakingView-Nodes`}
+                            style={{
+                                padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+                                margin: 'auto',
+                                width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+                                height: 'max-content',
+                                position: 'relative',
+                                gridGap: this.props.gridGap,
+                                gridTemplateColumns: templatecols,
+                                gridAutoRows: '0px',
+                            }}>
+                            {this.props.renderChildren(this.props.docList)}
+                        </div>
 
-              {!this.props.chromeHidden && type !== DocumentType.PRES ?
-                  <div className="collectionNoteTakingView-DocumentButtons"
-                      // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
-                      style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
-                      <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
-                          <EditableView
-                              GetValue={returnEmptyString}
-                              SetValue={this.addNewTextDoc}
-                              textCallback={this.textCallback}
-                              placeholder={"Type ':' for commands"}
-                              contents={"+ New Node"}
-                              menuCallback={this.menuCallback}
-                          />
-                      </div>
-                      <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
-                          <EditableView {...this.props.editableViewProps} />
-                      </div>
-                      {(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) && 
-                          <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
-                          <FontAwesomeIcon icon="trash" size="lg" />
-                      </button>
-                      }
-                  </div>
-                  : null}
-              </div>
-            }
-        </>;
+                        {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+                            <div
+                                className="collectionNoteTakingView-DocumentButtons"
+                                // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
+                                style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
+                                <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
+                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
+                                </div>
+                                <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
+                                    <EditableView {...this.props.editableViewProps} />
+                                </div>
+                                {this.props.columnHeaders?.length && this.props.columnHeaders.length > 1 && (
+                                    <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+                                        <FontAwesomeIcon icon="trash" size="lg" />
+                                    </button>
+                                )}
+                            </div>
+                        ) : null}
+                    </div>
+                }
+            </>
+        );
     }
 
-
     render() {
         TraceMobx();
         const heading = this._heading;
         return (
-            <div className={"collectionNoteTakingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
+            <div
+                className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+                key={heading}
                 style={{
-                  //TODO: change this so that it's based on the column width 
+                    //TODO: change this so that it's based on the column width
                     width: this.columnWidth,
-                    height: "100%",
-                    background: this._background
+                    height: '100%',
+                    background: this._background,
                 }}
-                ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+                ref={this.createColumnDropRef}
+                onPointerEnter={this.pointerEntered}
+                onPointerLeave={this.pointerLeave}>
                 {this.innards}
-            </div >
+            </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index ed5dc3715..7d31b3193 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,61 +1,63 @@
-import { action, observable } from "mobx";
-import * as React from "react";
+import { action, observable } from 'mobx';
+import * as React from 'react';
 
 interface DividerProps {
-  index: number
-  xMargin: number
-  setColumnStartXCoords: (movementX: number, colIndex: number) => void
+    index: number;
+    xMargin: number;
+    setColumnStartXCoords: (movementX: number, colIndex: number) => void;
 }
 
-export default class Divider extends React.Component<DividerProps>{
-  @observable private isHoverActive = false;
-  @observable private isResizingActive = false;
-  
-  @action
-  private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
-      e.stopPropagation();
-      e.preventDefault();
-      window.removeEventListener("pointermove", this.onPointerMove);
-      window.removeEventListener("pointerup", this.onPointerUp);
-      window.addEventListener("pointermove", this.onPointerMove);
-      window.addEventListener("pointerup", this.onPointerUp);
-      this.isResizingActive = true;
-  }
+export class CollectionNoteTakingViewDivider extends React.Component<DividerProps> {
+    @observable private isHoverActive = false;
+    @observable private isResizingActive = false;
 
-  @action
-  private onPointerUp = () => {
-      this.isResizingActive = false;
-      this.isHoverActive = false;
-      window.removeEventListener("pointermove", this.onPointerMove);
-      window.removeEventListener("pointerup", this.onPointerUp);
-  }
+    @action
+    private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+        e.stopPropagation();
+        e.preventDefault();
+        window.removeEventListener('pointermove', this.onPointerMove);
+        window.removeEventListener('pointerup', this.onPointerUp);
+        window.addEventListener('pointermove', this.onPointerMove);
+        window.addEventListener('pointerup', this.onPointerUp);
+        this.isResizingActive = true;
+    };
 
-  @action
-  onPointerMove = ({ movementX }: PointerEvent) => {
-    this.props.setColumnStartXCoords(movementX, this.props.index)
-  }
-  
-  render() {
-    return (
-      <div className="columnResizer"
-        style={{
-          display: "flex", 
-          alignItems: "center", 
-          cursor: "col-resize"
-        }}
-        onPointerEnter={action(() => this.isHoverActive = true)}
-        onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
-      >
-        <div className="columnResizer-handler" onPointerDown={e => this.registerResizing(e)}
-          style={{
-            height: "95%",
-            width: 12,
-            borderRight: "4px solid #282828",
-            borderLeft: "4px solid #282828",
-            margin: "0px 10px"
-          }}
-        />
-      </div>
-    )
-  }
-}
\ No newline at end of file
+    @action
+    private onPointerUp = () => {
+        this.isResizingActive = false;
+        this.isHoverActive = false;
+        window.removeEventListener('pointermove', this.onPointerMove);
+        window.removeEventListener('pointerup', this.onPointerUp);
+    };
+
+    @action
+    onPointerMove = ({ movementX }: PointerEvent) => {
+        this.props.setColumnStartXCoords(movementX, this.props.index);
+    };
+
+    render() {
+        return (
+            <div
+                className="columnResizer"
+                style={{
+                    display: 'flex',
+                    alignItems: 'center',
+                    cursor: 'col-resize',
+                }}
+                onPointerEnter={action(() => (this.isHoverActive = true))}
+                onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+                <div
+                    className="columnResizer-handler"
+                    onPointerDown={e => this.registerResizing(e)}
+                    style={{
+                        height: '95%',
+                        width: 12,
+                        borderRight: '4px solid #282828',
+                        borderLeft: '4px solid #282828',
+                        margin: '0px 10px',
+                    }}
+                />
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1927db51e..ef68cadd7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -447,8 +447,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them.
 
                 const docs = this.childDocList;
-                // reset drag manager docs, because we just dropped
-                DragManager.docsBeingDragged = [];
                 // still figuring out where to add the document
                 if (docs && newDocs.length) {
                     const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter;
@@ -456,6 +454,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1));
                     docs.splice(insertInd - offset, 0, ...newDocs);
                 }
+                // reset drag manager docs, because we just dropped
+                DragManager.docsBeingDragged.length = 0;
             }
         } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index afb618b34..1ee1aec5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -156,9 +156,7 @@ export interface DocumentViewSharedProps {
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
     updateFilterDoc?: (doc: Doc) => void;
-    // Parker added both of these
-    originalBackgroundColor?: string;
-    isNoteTakingView?: boolean;
+    dontHideOnDrag?: boolean;
 }
 
 // these props are specific to DocuentViews
@@ -494,12 +492,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
-            if (this.props.isNoteTakingView) {
-                dragData.draggedDocuments.forEach((doc) => {
-                doc.backgroundColor = "#C9DAEF";
-                doc.opacity = 0.5;
-              });
-            }
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
             dragData.offset = this.props
                 .ScreenToLocalTransform()
@@ -511,22 +503,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.treeViewDoc = this.props.treeViewDoc;
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
-            //dragData.dimSource : 
-            // dragEffects field, set dim 
+            //dragData.dimSource :
+            // dragEffects field, set dim
             // add kv pairs to a doc, swap properties with the node while dragging, and then swap when dropping
             // add a dragEffects prop to DocumentView as a function that sets up. Each view has its own prop, when you start dragging:
-            // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns  
+            // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns
             const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-            ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));            
-            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStar && !this.props.isNoteTakingView)},
-                () => setTimeout(action(() => {
-                  ffview && (ffview.ChildDrag = undefined)
-                  //TODO: is there a better way than adding another field to the props? Not quite sure how "this" works tbh
-                  if (this.props.isNoteTakingView) {
-                    this.props.Document.backgroundColor = "";
-                    this.props.Document.opacity = 1;
-                  }
-                }))); // this needs to happen after the drop event is processed.
+            ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
+            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () =>
+                setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
+            ); // this needs to happen after the drop event is processed.
             ffview?.setupDragLines(false);
         }
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b8ee89ef2..add83f4e0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1001,7 +1001,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
             ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
                 const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
-                autoHeight && newHeight && this.props.setHeight?.(newHeight);
+                if (autoHeight && newHeight && newHeight !== this.rootDoc.height) {
+                    this.props.setHeight?.(newHeight);
+                }
             },
             { fireImmediately: true }
         );
@@ -1695,7 +1697,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight);
-                if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+                if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
                     setScrollHeight();
                 } else {
                     setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
-- 
cgit v1.2.3-70-g09d2


From 26670c8b9eb6e2fd981c3a0997bff5556b60504b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Aug 2022 09:34:51 -0400
Subject: made props.showTitle = '' override doc.showTitle.  fixed
 de-autoLinking when text box is empty

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

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a83163eb0..3bd4f5152 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -124,6 +124,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return (
                 (doc &&
                     !doc.presentationTargetDoc &&
+                    props?.showTitle?.() !== '' &&
                     StrCast(
                         doc._showTitle,
                         props?.showTitle?.() ||
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index a12564839..85b1f8d9e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import wiki from 'wikijs';
 import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -236,6 +236,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             isDocumentActive={returnFalse}
                             isContentActive={returnFalse}
                             addDocument={returnFalse}
+                            showTitle={returnEmptyString}
                             removeDocument={returnFalse}
                             addDocTab={returnFalse}
                             pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index add83f4e0..a018f51c2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -382,9 +382,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     autoLink = () => {
+        const newAutoLinks = new Set<Doc>();
+        const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
         if (this._editorView?.state.doc.textContent) {
-            const newAutoLinks = new Set<Doc>();
-            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
             const f = this._editorView.state.selection.from;
             const t = this._editorView.state.selection.to;
             var tr = this._editorView.state.tr as any;
@@ -393,8 +393,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
-            oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
         }
+        oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
     };
 
     updateTitle = () => {
-- 
cgit v1.2.3-70-g09d2


From 2a1d6793d33d9c72295d23965aefd23f2004bbcb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Aug 2022 11:48:27 -0400
Subject: cleaning up link ui.   linkPreviews (not from linkMenu) have an edit
 button.  link lines work when in lightboxview.

---
 .../CollectionFreeFormLinkView.tsx                 |   5 +-
 .../CollectionFreeFormLinksView.tsx                |   5 +-
 src/client/views/linking/LinkEditor.scss           |  65 +++++-----
 src/client/views/linking/LinkEditor.tsx            | 141 ++++++++++++---------
 src/client/views/linking/LinkMenuItem.tsx          |  89 +------------
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  16 ++-
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  18 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 10 files changed, 149 insertions(+), 198 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index d979ef961..bf9de6760 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -195,7 +195,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
         const aleft = this.visibleX(adiv);
         const bleft = this.visibleX(bdiv);
-        const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
+        const aclipped = aleft !== a.left || atop !== a.top;
+        const bclipped = bleft !== b.left || btop !== b.top;
+        if (aclipped && bclipped) return undefined;
+        const clipped = aclipped || bclipped;
         const pt1 = [aleft + a.width / 2, atop + a.height / 2];
         const pt2 = [bleft + b.width / 2, btop + b.width / 2];
         const pt1vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt1[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt1[1]];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 8720c9097..b8344dc0c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -5,11 +5,14 @@ import { DocumentManager } from '../../../util/DocumentManager';
 import './CollectionFreeFormLinksView.scss';
 import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
 import React = require('react');
+import { LightboxView } from '../../LightboxView';
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> {
     @computed get uniqueConnections() {
-        return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
+        return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
+            .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath)))
+            .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
     }
 
     render() {
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 1d6496d3c..b0ee4e46d 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -1,10 +1,11 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
 
 .linkEditor {
     width: 100%;
     height: auto;
     font-size: 13px; // TODO
     user-select: none;
+    max-width: 280px;
 }
 
 .linkEditor-button-back {
@@ -20,19 +21,20 @@
 }
 
 .linkEditor-info {
-    //border-bottom: 0.5px solid $light-gray;
-    //padding-bottom: 1px;
-    padding: 12px;
+    padding-top: 12px;
     padding-left: 5px;
+    padding-bottom: 3px;
     //margin-bottom: 6px;
     display: flex;
     justify-content: space-between;
     color: black;
 
     .linkEditor-linkedTo {
-        width: calc(100% - 26px);
-        padding-left: 5px;
-        padding-right: 5px;
+        width: calc(100% - 46px);
+        overflow: hidden;
+        position: relative;
+        text-overflow: ellipsis;
+        white-space: pre;
 
         .linkEditor-downArrow {
             &:hover {
@@ -77,11 +79,14 @@
         width: 20px;
     }
 }
+.linkEditor-deleteBtn {
+    padding-left: 3px;
+}
 
 .linkEditor-description {
     padding-left: 26px;
-    padding-right: 6.5px;
     padding-bottom: 3.5px;
+    display: flex;
 
     .linkEditor-description-label {
         text-decoration-color: black;
@@ -96,7 +101,6 @@
             //border: 1px solid grey;
             //border-radius: 4px;
             padding-left: 2px;
-            padding-right: 2px;
             //margin-right: 4px;
             color: black;
             text-decoration-color: grey;
@@ -104,17 +108,13 @@
 
         .linkEditor-description-add-button {
             display: inline;
-            /* float: right; */
             border-radius: 7px;
             font-size: 9px;
             background: black;
-            /* padding: 3px; */
-            padding-top: 4px;
-            padding-left: 7px;
-            padding-bottom: 4px;
-            padding-right: 8px;
             height: 80%;
             color: white;
+            padding: 3px;
+            margin-left: 3px;
 
             &:hover {
                 cursor: pointer;
@@ -146,6 +146,7 @@
     padding-left: 26px;
     padding-right: 6.5px;
     padding-bottom: 15px;
+    display: flex;
 
     &:hover {
         cursor: pointer;
@@ -153,12 +154,11 @@
 
     .linkEditor-followingDropdown-label {
         color: black;
+        padding-right: 3px;
     }
 
     .linkEditor-followingDropdown-dropdown {
-
         .linkEditor-followingDropdown-header {
-
             border: 1px solid grey;
             border-radius: 4px;
             //background-color: rgb(236, 236, 236);
@@ -195,27 +195,25 @@
                     background-color: rgb(187, 220, 231);
                 }
             }
-
         }
     }
-
-
 }
 
-
-
-
-
-
 .linkEditor-button,
 .linkEditor-addbutton {
-    width: 18px;
-    height: 18px;
-    padding: 0;
-    // font-size: 12px;
-    border-radius: 10px;
-
-
+    width: 15%;
+    border-radius: 7px;
+    font-size: 9px;
+    background: black;
+    padding: 3px;
+    height: 80%;
+    color: white;
+    text-align: center;
+    margin: auto;
+    margin-left: 3px;
+    > svg {
+        margin: auto;
+    }
     &:disabled {
         background-color: gray;
     }
@@ -270,7 +268,6 @@
     }
 }
 
-
 .linkEditor-dropdown {
     width: 100%;
     position: relative;
@@ -334,4 +331,4 @@
     .linkEditor-button {
         margin-left: 3px;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ba301962b..d23d38854 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -9,11 +9,12 @@ import { undoBatch } from '../../util/UndoManager';
 import './LinkEditor.scss';
 import { LinkRelationshipSearch } from './LinkRelationshipSearch';
 import React = require('react');
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
 
 interface LinkEditorProps {
     sourceDoc: Doc;
     linkDoc: Doc;
-    showLinks: () => void;
+    showLinks?: () => void;
     hideback?: boolean;
 }
 @observer
@@ -22,13 +23,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
     @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom);
     @observable openDropdown: boolean = false;
-    @observable showInfo: boolean = false;
-    @computed get infoIcon() {
-        if (this.showInfo) {
-            return 'chevron-up';
-        }
-        return 'chevron-down';
-    }
     @observable private buttonColor: string = '';
     @observable private relationshipButtonColor: string = '';
     @observable private relationshipSearchVisibility: string = 'none';
@@ -36,12 +30,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
     //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
 
-    @undoBatch
-    deleteLink = (): void => {
-        LinkManager.Instance.deleteLink(this.props.linkDoc);
-        this.props.showLinks();
-    };
-
     @undoBatch
     setRelationshipValue = action((value: string) => {
         if (LinkManager.currentLink) {
@@ -173,7 +161,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
         return (
             <div className="linkEditor-description">
-                <div className="linkEditor-description-label">Link Relationship:</div>
+                <div className="linkEditor-description-label">Relationship:</div>
                 <div className="linkEditor-description-input">
                     <div className="linkEditor-description-editing">
                         <input
@@ -214,7 +202,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     get editDescription() {
         return (
             <div className="linkEditor-description">
-                <div className="linkEditor-description-label">Link Description:</div>
+                <div className="linkEditor-description-label">Description:</div>
                 <div className="linkEditor-description-input">
                     <div className="linkEditor-description-editing">
                         <input style={{ width: '100%' }} autoComplete={'off'} id="input" value={this.description} placeholder={'Enter link description'} onKeyDown={this.onDescriptionKey} onChange={this.handleDescriptionChange}></input>
@@ -284,68 +272,99 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         );
     }
 
-    @action
-    changeInfo = () => {
-        this.showInfo = !this.showInfo;
+    autoMove = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))));
+    };
+
+    showAnchor = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden))));
+    };
+
+    showLink = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))));
+    };
+
+    deleteLink = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc))));
     };
 
     render() {
         const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
 
         return !destination ? null : (
-            <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()}>
+            <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                 <div className="linkEditor-info">
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">Return to link menu</div>
-                            </>
-                        }
-                        placement="top">
-                        <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onClick={this.props.showLinks}>
-                            <FontAwesomeIcon icon="arrow-left" size="sm" />{' '}
-                        </button>
-                    </Tooltip>
+                    {!this.props.showLinks ? null : (
+                        <Tooltip title={<div className="dash-tooltip">Return to link menu</div>} placement="top">
+                            <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.props.showLinks?.())}>
+                                <FontAwesomeIcon icon="arrow-left" size="sm" />{' '}
+                            </button>
+                        </Tooltip>
+                    )}
                     <p className="linkEditor-linkedTo">
                         Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b>
                     </p>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">Show more link information</div>
-                            </>
-                        }
-                        placement="top">
-                        <div className="linkEditor-downArrow">
-                            <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} />
+                    <Tooltip title={<div className="dash-tooltip">Delete Link</div>}>
+                        <div className="linkEditor-deleteBtn" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
+                            <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
                         </div>
                     </Tooltip>
                 </div>
-                {this.showInfo ? (
-                    <div className="linkEditor-moreInfo">
-                        <div>
-                            {this.props.linkDoc.author ? (
-                                <div>
-                                    {' '}
-                                    <b>Author:</b> {StrCast(this.props.linkDoc.author)}
-                                </div>
-                            ) : null}
-                        </div>
-                        <div>
-                            {this.props.linkDoc.creationDate ? (
-                                <div>
-                                    {' '}
-                                    <b>Creation Date:</b>
-                                    {DateCast(this.props.linkDoc.creationDate).toString()}
-                                </div>
-                            ) : null}
-                        </div>
-                    </div>
-                ) : null}
+                <div className="linkEditor-moreInfo">
+                    {this.props.linkDoc.author ? (
+                        <>
+                            {' '}
+                            <b>Author:</b> {StrCast(this.props.linkDoc.author)}
+                        </>
+                    ) : null}
+                    {this.props.linkDoc.creationDate ? (
+                        <>
+                            {' '}
+                            <b>Creation Date:</b>
+                            {DateCast(this.props.linkDoc.creationDate).toString()}
+                        </>
+                    ) : null}
+                </div>
 
                 {this.editDescription}
                 {this.editRelationship}
                 {this.editZoomFollow}
+                <div className="linkEditor-description">
+                    Show Anchor:
+                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div>}>
+                        <div
+                            className="linkEditor-button"
+                            style={{ background: this.props.linkDoc.hidden ? 'gray' : '#4476f7' /* $medium-blue                                       */ }}
+                            onPointerDown={this.showAnchor}
+                            onClick={e => e.stopPropagation()}>
+                            <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" />
+                        </div>
+                    </Tooltip>
+                </div>
+                <div className="linkEditor-description">
+                    Show Link Line:
+                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div>}>
+                        <div
+                            className="linkEditor-button"
+                            style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }}
+                            onPointerDown={this.showLink}
+                            onClick={e => e.stopPropagation()}>
+                            <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" />
+                        </div>
+                    </Tooltip>
+                </div>
+                <div className="linkEditor-description">
+                    Freeze Anchor:
+                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div>}>
+                        <div
+                            className="linkEditor-button"
+                            style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }}
+                            onPointerDown={this.autoMove}
+                            onClick={e => e.stopPropagation()}>
+                            <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" />
+                        </div>
+                    </Tooltip>
+                </div>
                 {this.followingDropdown}
             </div>
         );
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index ed856a4ab..60428fc98 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -125,33 +125,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         );
     };
 
-    deleteLink = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(
-            this,
-            e,
-            returnFalse,
-            emptyFunction,
-            undoBatch(
-                action(() => {
-                    this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-                    LinkManager.Instance.deleteLink(this.props.linkDoc);
-                })
-            )
-        );
-    };
-
-    autoMove = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))));
-    };
-
-    showLink = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))));
-    };
-
-    showAnchor = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden))));
-    };
-
     render() {
         const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp;
 
@@ -183,7 +156,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                 linkSrc: this.props.sourceDoc,
                                 linkDoc: this.props.linkDoc,
                                 showHeader: false,
-                                location: [e.clientX, e.clientY + 20],
+                                location: [this._drag.current?.getBoundingClientRect().right ?? 100, this._drag.current?.getBoundingClientRect().top ?? e.clientY],
                             })
                         }
                         onPointerDown={this.onLinkButtonDown}>
@@ -206,69 +179,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         </div>
 
                         <div className="linkMenu-item-buttons" ref={this._buttonRef}>
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div>
-                                    </>
-                                }>
-                                <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? '' : '#4476f7' /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
-                                    <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" />
-                                </div>
-                            </Tooltip>
-
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div>
-                                    </>
-                                }>
-                                <div
-                                    className="button"
-                                    ref={this._editRef}
-                                    style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }}
-                                    onPointerDown={this.showLink}
-                                    onClick={e => e.stopPropagation()}>
-                                    <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" />
-                                </div>
-                            </Tooltip>
-
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div>
-                                    </>
-                                }>
-                                <div
-                                    className="button"
-                                    ref={this._editRef}
-                                    style={{ background: this.props.linkDoc.hidden ? 'gray' : !this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }}
-                                    onPointerDown={this.autoMove}
-                                    onClick={e => e.stopPropagation()}>
-                                    <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" />
-                                </div>
-                            </Tooltip>
-
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">Edit Link</div>
-                                    </>
-                                }>
+                            <Tooltip title={<div className="dash-tooltip">Edit Link</div>}>
                                 <div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" />
                                 </div>
                             </Tooltip>
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">Delete Link</div>
-                                    </>
-                                }>
-                                <div className="button" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
-                                    <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
-                                </div>
-                            </Tooltip>
                         </div>
                     </div>
                 </div>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 5939d1680..a37de7f69 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -307,7 +307,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             <div
                 className="documentLinksButton-wrapper"
                 style={{
-                    transform: this.props.InMenu ? undefined : `scale(${this.props.scaling})`,
+                    transform: `scale(${this.props.scaling?.() || 1})`,
                 }}>
                 {(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? (
                     <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1ee1aec5a..2524d66dd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1070,7 +1070,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     <DocumentLinksButton
                         View={this.props.DocumentView()}
                         scaling={this.linkButtonInverseScaling}
-                        Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -30]}
+                        Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -28]}
                     />
                 )}
                 {audioView}
@@ -1298,6 +1298,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     isHovering = () => this._isHovering;
     @observable _isHovering = false;
     @observable _: string = '';
+    _hoverTimeout: any = undefined;
     @computed get renderDoc() {
         TraceMobx();
         const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
@@ -1308,8 +1309,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 <div
                     className={`documentView-node${this.topMost ? '-topmost' : ''}`}
                     id={this.props.Document[Id]}
-                    onPointerEnter={action(() => (this._isHovering = true))}
-                    onPointerLeave={action(() => (this._isHovering = false))}
+                    onPointerEnter={action(() => {
+                        clearTimeout(this._hoverTimeout);
+                        this._isHovering = true;
+                    })}
+                    onPointerLeave={action(() => {
+                        clearTimeout(this._hoverTimeout);
+                        this._hoverTimeout = setTimeout(
+                            action(() => (this._isHovering = false)),
+                            500
+                        );
+                    })}
                     style={{
                         background: isButton || thumb ? undefined : this.backgroundColor,
                         opacity: this.opacity,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 85a8622ec..5102eae51 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -136,7 +136,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className={`linkAnchorBox-cont${small ? '-small' : ''}`}
-                onPointerLeave={LinkDocPreview.Clear}
+                //onPointerLeave={} //LinkDocPreview.Clear}
                 onPointerEnter={e =>
                     LinkDocPreview.SetLinkInfo({
                         docProps: this.props,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 85b1f8d9e..93ca22d5d 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -17,6 +17,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { DocumentView, DocumentViewSharedProps } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
+import { LinkEditor } from '../linking/LinkEditor';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -118,13 +119,15 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         }
         return undefined;
     }
-    deleteLink = (e: React.PointerEvent) => {
+    @observable _showEditor = false;
+    editLink = (e: React.PointerEvent): void => {
+        LinkManager.currentLink = this.props.linkDoc;
         setupMoveUpEvents(
             this,
             e,
             returnFalse,
             emptyFunction,
-            undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))
+            action(() => (this._showEditor = !this._showEditor))
         );
     };
     nextHref = (e: React.PointerEvent) => {
@@ -183,9 +186,9 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                         </div>
                     </Tooltip>
 
-                    <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
-                        <div className="linkDocPreview-button" onPointerDown={this.deleteLink}>
-                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
+                    <Tooltip title={<div className="dash-tooltip">Edit Link</div>} placement="top">
+                        <div className="linkDocPreview-button" onPointerDown={this.editLink}>
+                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="edit" color="white" size="sm" />
                         </div>
                     </Tooltip>
                 </div>
@@ -271,8 +274,9 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 className="linkDocPreview"
                 ref={this._linkDocRef}
                 onPointerDown={this.followLinkPointerDown}
-                style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
-                {this.docPreview}
+                style={{ left: this.props.location[0], top: this.props.location[1], width: this._showEditor ? 'auto' : this.width() + borders, height: this._showEditor ? 'max-content' : this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+                {this._showEditor ? null : this.docPreview}
+                {!this._showEditor || !this._linkSrc || !this._linkDoc ? null : <LinkEditor sourceDoc={this._linkSrc} linkDoc={this._linkDoc} showLinks={action(() => (this._showEditor = !this._showEditor))} />}
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a018f51c2..40b0d285c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -24,7 +24,7 @@ import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction,
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
@@ -1751,7 +1751,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
-            const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'tree' ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+            const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
-- 
cgit v1.2.3-70-g09d2


From 4b2f131d91e71f4514642b5846713e9c3b68210b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Aug 2022 12:54:06 -0400
Subject: fixed autolinking to not match partial word substrings.  restored
 textbox dictation.

---
 src/client/documents/Documents.ts                  |   5 +-
 src/client/util/DictationManager.ts                | 439 +++++++++++----------
 src/client/views/DocumentDecorations.tsx           |   1 -
 src/client/views/linking/LinkMenuItem.tsx          |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  96 +++--
 5 files changed, 282 insertions(+), 263 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9d00664ad..c7ea04839 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1287,10 +1287,11 @@ export namespace DocUtils {
 
     export let ActiveRecordings: { props: FieldViewProps; getAnchor: () => Doc }[] = [];
 
-    export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
+    export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
         broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
         return DocUtils.ActiveRecordings.map(audio => {
-            const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
+            const sourceDoc = getSourceDoc();
+            const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
             link && (link.followLinkLocation = 'add:right');
             return link;
         });
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index a6dcda4bc..13f036838 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,37 +1,35 @@
-import * as interpreter from "words-to-numbers";
+import * as interpreter from 'words-to-numbers';
 // @ts-ignore  bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
-import type { } from "@types/dom-speech-recognition";
-import { Doc, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { RichTextField } from "../../fields/RichTextField";
-import { listSpec } from "../../fields/Schema";
-import { Cast, CastCtor } from "../../fields/Types";
-import { AudioField, ImageField } from "../../fields/URLField";
-import { Utils } from "../../Utils";
-import { Docs } from "../documents/Documents";
-import { DocumentType } from "../documents/DocumentTypes";
-import { DictationOverlay } from "../views/DictationOverlay";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { SelectionManager } from "./SelectionManager";
-import { UndoManager } from "./UndoManager";
-
+import type {} from '@types/dom-speech-recognition';
+import { Doc, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { RichTextField } from '../../fields/RichTextField';
+import { listSpec } from '../../fields/Schema';
+import { Cast, CastCtor } from '../../fields/Types';
+import { AudioField, ImageField } from '../../fields/URLField';
+import { Utils } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { SelectionManager } from './SelectionManager';
+import { UndoManager } from './UndoManager';
 
 /**
  * This namespace provides a singleton instance of a manager that
  * handles the listening and text-conversion of user speech.
- * 
+ *
  * The basic manager functionality can be attained by the DictationManager.Controls namespace, which provide
  * a simple recording operation that returns the interpreted text as a string.
- * 
+ *
  * Additionally, however, the DictationManager also exposes the ability to execute voice commands within Dash.
  * It stores a default library of registered commands that can be triggered by listen()'ing for a phrase and then
  * passing the results into the execute() function.
- * 
+ *
  * In addition to compile-time default commands, you can invoke DictationManager.Commands.Register(Independent|Dependent)
  * to add new commands as classes or components are constructed.
  */
 export namespace DictationManager {
-
     /**
      * Some type maneuvering to access Webkit's built-in
      * speech recognizer.
@@ -42,27 +40,26 @@ export namespace DictationManager {
         }
     }
     const { webkitSpeechRecognition }: CORE.IWindow = window as any as CORE.IWindow;
-    export const placeholder = "Listening...";
+    export const placeholder = 'Listening...';
 
     export namespace Controls {
-
-        export const Infringed = "unable to process: dictation manager still involved in previous session";
+        export const Infringed = 'unable to process: dictation manager still involved in previous session';
         const browser = (() => {
             const identifier = navigator.userAgent.toLowerCase();
-            if (identifier.indexOf("safari") >= 0) {
-                return "Safari";
+            if (identifier.indexOf('safari') >= 0) {
+                return 'Safari';
             }
-            if (identifier.indexOf("chrome") >= 0) {
-                return "Chrome";
+            if (identifier.indexOf('chrome') >= 0) {
+                return 'Chrome';
             }
-            if (identifier.indexOf("firefox") >= 0) {
-                return "Firefox";
+            if (identifier.indexOf('firefox') >= 0) {
+                return 'Firefox';
             }
-            return "Unidentified Browser";
+            return 'Unidentified Browser';
         })();
         const unsupported = `listening is not supported in ${browser}`;
-        const intraSession = ". ";
-        const interSession = " ... ";
+        const intraSession = '. ';
+        const interSession = ' ... ';
 
         export let isListening = false;
         let isManuallyStopped = false;
@@ -74,7 +71,7 @@ export namespace DictationManager {
 
         export type InterimResultHandler = (results: string) => any;
         export type ContinuityArgs = { indefinite: boolean } | false;
-        export type DelimiterArgs = { inter: string, intra: string };
+        export type DelimiterArgs = { inter: string; intra: string };
         export type ListeningUIStatus = { interim: boolean } | false;
 
         export interface ListeningOptions {
@@ -105,21 +102,21 @@ export namespace DictationManager {
             try {
                 results = await (pendingListen = listenImpl(options));
                 pendingListen = undefined;
-                // if (results) {
-                //     Utils.CopyText(results);
-                //     if (overlay) {
-                //         DictationOverlay.Instance.isListening = false;
-                //         const execute = options?.tryExecute;
-                //         DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
-                //         DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
-                //     }
-                //     options?.tryExecute && await DictationManager.Commands.execute(results);
-                // }
+                if (results) {
+                    Utils.CopyText(results);
+                    if (overlay) {
+                        DictationOverlay.Instance.isListening = false;
+                        const execute = options?.tryExecute;
+                        DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
+                        DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+                    }
+                    options?.tryExecute && (await DictationManager.Commands.execute(results));
+                }
             } catch (e: any) {
                 console.log(e);
                 if (overlay) {
                     DictationOverlay.Instance.isListening = false;
-                    DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+                    DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${'error' in e ? e.error : 'unknown error'}`;
                     DictationOverlay.Instance.dictationSuccess = false;
                 }
             } finally {
@@ -131,7 +128,7 @@ export namespace DictationManager {
 
         const listenImpl = (options?: Partial<ListeningOptions>) => {
             if (!recognizer) {
-                console.log("DictationManager:" + unsupported);
+                console.log('DictationManager:' + unsupported);
                 return unsupported;
             }
             if (isListening) {
@@ -146,16 +143,18 @@ export namespace DictationManager {
             const intra = options?.delimiters?.intra;
             const inter = options?.delimiters?.inter;
 
-            recognizer.onstart = () => console.log("initiating speech recognition session...");
+            recognizer.onstart = () => console.log('initiating speech recognition session...');
             recognizer.interimResults = handler !== undefined;
             recognizer.continuous = continuous === undefined ? false : continuous !== false;
-            recognizer.lang = language === undefined ? "en-US" : language;
+            recognizer.lang = language === undefined ? 'en-US' : language;
 
             recognizer.start();
 
             return new Promise<string>((resolve, reject) => {
-                recognizer.onerror = (e: any) => { // e is SpeechRecognitionError but where is that defined? 
-                    if (!(indefinite && e.error === "no-speech")) {
+                recognizer.onerror = (e: any) => {
+                    console.log('SPEECH error:', encodeURIComponent);
+                    // e is SpeechRecognitionError but where is that defined?
+                    if (!(indefinite && e.error === 'no-speech')) {
                         recognizer.stop();
                         resolve(e);
                         //reject(e);
@@ -163,9 +162,10 @@ export namespace DictationManager {
                 };
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
+                    console.log('RESULT: ', e);
                     current = synthesize(e, intra);
                     let matchedTerminator: string | undefined;
-                    if (options?.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+                    if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
                         current = matchedTerminator;
                         recognizer.abort();
                         return complete();
@@ -175,6 +175,7 @@ export namespace DictationManager {
                 };
 
                 recognizer.onend = (e: Event) => {
+                    console.log('END: ', e);
                     if (!indefinite || isManuallyStopped) {
                         return complete();
                     }
@@ -187,11 +188,12 @@ export namespace DictationManager {
                 };
 
                 const complete = () => {
+                    console.log('COMPLETE:');
                     if (indefinite) {
                         current && sessionResults.push(current);
                         sessionResults.length && resolve(sessionResults.join(inter || interSession));
                     } else {
-                        resolve(current || "");
+                        resolve(current || '');
                     }
                     current = undefined;
                     sessionResults = [];
@@ -201,7 +203,6 @@ export namespace DictationManager {
                     recognizer.onerror = null;
                     recognizer.onend = null;
                 };
-
             });
         };
 
@@ -222,171 +223,173 @@ export namespace DictationManager {
             }
             return transcripts.join(delimiter || intraSession);
         };
-
     }
 
-    // export namespace Commands {
-
-    //     export const dictationFadeDuration = 2000;
-
-    //     export type IndependentAction = (target: DocumentView) => any | Promise<any>;
-    //     export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
-
-    //     export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
-    //     export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
-
-    //     export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
-    //     export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
-
-    //     export const execute = async (phrase: string) => {
-    //         return UndoManager.RunInBatch(async () => {
-    //             const targets = SelectionManager.Views();
-    //             if (!targets || !targets.length) {
-    //                 return;
-    //             }
-
-    //             phrase = phrase.toLowerCase();
-    //             const entry = Independent.get(phrase);
-
-    //             if (entry) {
-    //                 let success = false;
-    //                 const restrictTo = entry.restrictTo;
-    //                 for (const target of targets) {
-    //                     if (!restrictTo || validate(target, restrictTo)) {
-    //                         await entry.action(target);
-    //                         success = true;
-    //                     }
-    //                 }
-    //                 return success;
-    //             }
-
-    //             for (const entry of Dependent) {
-    //                 const regex = entry.expression;
-    //                 const matches = regex.exec(phrase);
-    //                 regex.lastIndex = 0;
-    //                 if (matches !== null) {
-    //                     let success = false;
-    //                     const restrictTo = entry.restrictTo;
-    //                     for (const target of targets) {
-    //                         if (!restrictTo || validate(target, restrictTo)) {
-    //                             await entry.action(target, matches);
-    //                             success = true;
-    //                         }
-    //                     }
-    //                     return success;
-    //                 }
-    //             }
-
-    //             return false;
-    //         }, "Execute Command");
-    //     };
-
-    //     const ConstructorMap = new Map<DocumentType, CastCtor>([
-    //         [DocumentType.COL, listSpec(Doc)],
-    //         [DocumentType.AUDIO, AudioField],
-    //         [DocumentType.IMG, ImageField],
-    //         [DocumentType.IMPORT, listSpec(Doc)],
-    //         [DocumentType.RTF, "string"]
-    //     ]);
-
-    //     const tryCast = (view: DocumentView, type: DocumentType) => {
-    //         const ctor = ConstructorMap.get(type);
-    //         if (!ctor) {
-    //             return false;
-    //         }
-    //         return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
-    //     };
-
-    //     const validate = (target: DocumentView, types: DocumentType[]) => {
-    //         for (const type of types) {
-    //             if (tryCast(target, type)) {
-    //                 return true;
-    //             }
-    //         }
-    //         return false;
-    //     };
-
-    //     const interpretNumber = (number: string) => {
-    //         const initial = parseInt(number);
-    //         if (!isNaN(initial)) {
-    //             return initial;
-    //         }
-    //         const converted = interpreter.wordsToNumbers(number, { fuzzy: true });
-    //         if (converted === null) {
-    //             return NaN;
-    //         }
-    //         return typeof converted === "string" ? parseInt(converted) : converted;
-    //     };
-
-    //     const Independent = new Map<string, IndependentEntry>([
-
-    //         ["clear", {
-    //             action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(),
-    //             restrictTo: [DocumentType.COL]
-    //         }],
-
-    //         ["open fields", {
-    //             action: (target: DocumentView) => {
-    //                 const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
-    //                 target.props.addDocTab(kvp, "add:right");
-    //             }
-    //         }],
-
-    //         ["new outline", {
-    //             action: (target: DocumentView) => {
-    //                 const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true });
-    //                 const proto = newBox.proto!;
-    //                 const prompt = "Press alt + r to start dictating here...";
-    //                 const head = 3;
-    //                 const anchor = head + prompt.length;
-    //                 const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
-    //                 proto.data = new RichTextField(proseMirrorState);
-    //                 proto.backgroundColor = "#eeffff";
-    //                 target.props.addDocTab(newBox, "add:right");
-    //             }
-    //         }]
-
-    //     ]);
-
-    //     const Dependent = new Array<DependentEntry>(
-
-    //         {
-    //             expression: /create (\w+) documents of type (image|nested collection)/g,
-    //             action: (target: DocumentView, matches: RegExpExecArray) => {
-    //                 const count = interpretNumber(matches[1]);
-    //                 const what = matches[2];
-    //                 const dataDoc = Doc.GetProto(target.props.Document);
-    //                 const fieldKey = "data";
-    //                 if (isNaN(count)) {
-    //                     return;
-    //                 }
-    //                 for (let i = 0; i < count; i++) {
-    //                     let created: Doc | undefined;
-    //                     switch (what) {
-    //                         case "image":
-    //                             created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
-    //                             break;
-    //                         case "nested collection":
-    //                             created = Docs.Create.FreeformDocument([], {});
-    //                             break;
-    //                     }
-    //                     created && Doc.AddDocToList(dataDoc, fieldKey, created);
-    //                 }
-    //             },
-    //             restrictTo: [DocumentType.COL]
-    //         },
-
-    //         {
-    //             expression: /view as (freeform|stacking|masonry|schema|tree)/g,
-    //             action: (target: DocumentView, matches: RegExpExecArray) => {
-    //                 const mode = matches[1];
-    //                 mode && (target.props.Document._viewType = mode);
-    //             },
-    //             restrictTo: [DocumentType.COL]
-    //         }
-
-    //     );
-
-    // }
-
-}
\ No newline at end of file
+    export namespace Commands {
+        export const dictationFadeDuration = 2000;
+
+        export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+        export type IndependentEntry = { action: IndependentAction; restrictTo?: DocumentType[] };
+
+        export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+        export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] };
+
+        export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
+        export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+
+        export const execute = async (phrase: string) => {
+            return UndoManager.RunInBatch(async () => {
+                console.log('PHRASE: ' + phrase);
+                const targets = SelectionManager.Views();
+                if (!targets || !targets.length) {
+                    return;
+                }
+
+                phrase = phrase.toLowerCase();
+                const entry = Independent.get(phrase);
+
+                if (entry) {
+                    let success = false;
+                    const restrictTo = entry.restrictTo;
+                    for (const target of targets) {
+                        if (!restrictTo || validate(target, restrictTo)) {
+                            await entry.action(target);
+                            success = true;
+                        }
+                    }
+                    return success;
+                }
+
+                for (const entry of Dependent) {
+                    const regex = entry.expression;
+                    const matches = regex.exec(phrase);
+                    regex.lastIndex = 0;
+                    if (matches !== null) {
+                        let success = false;
+                        const restrictTo = entry.restrictTo;
+                        for (const target of targets) {
+                            if (!restrictTo || validate(target, restrictTo)) {
+                                await entry.action(target, matches);
+                                success = true;
+                            }
+                        }
+                        return success;
+                    }
+                }
+
+                return false;
+            }, 'Execute Command');
+        };
+
+        const ConstructorMap = new Map<DocumentType, CastCtor>([
+            [DocumentType.COL, listSpec(Doc)],
+            [DocumentType.AUDIO, AudioField],
+            [DocumentType.IMG, ImageField],
+            [DocumentType.IMPORT, listSpec(Doc)],
+            [DocumentType.RTF, 'string'],
+        ]);
+
+        const tryCast = (view: DocumentView, type: DocumentType) => {
+            const ctor = ConstructorMap.get(type);
+            if (!ctor) {
+                return false;
+            }
+            return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
+        };
+
+        const validate = (target: DocumentView, types: DocumentType[]) => {
+            for (const type of types) {
+                if (tryCast(target, type)) {
+                    return true;
+                }
+            }
+            return false;
+        };
+
+        const interpretNumber = (number: string) => {
+            const initial = parseInt(number);
+            if (!isNaN(initial)) {
+                return initial;
+            }
+            const converted = interpreter.wordsToNumbers(number, { fuzzy: true });
+            if (converted === null) {
+                return NaN;
+            }
+            return typeof converted === 'string' ? parseInt(converted) : converted;
+        };
+
+        const Independent = new Map<string, IndependentEntry>([
+            [
+                'clear',
+                {
+                    action: (target: DocumentView) => (Doc.GetProto(target.props.Document).data = new List()),
+                    restrictTo: [DocumentType.COL],
+                },
+            ],
+
+            [
+                'open fields',
+                {
+                    action: (target: DocumentView) => {
+                        const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
+                        target.props.addDocTab(kvp, 'add:right');
+                    },
+                },
+            ],
+
+            [
+                'new outline',
+                {
+                    action: (target: DocumentView) => {
+                        const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _autoHeight: true });
+                        const proto = newBox.proto!;
+                        const prompt = 'Press alt + r to start dictating here...';
+                        const head = 3;
+                        const anchor = head + prompt.length;
+                        const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
+                        proto.data = new RichTextField(proseMirrorState);
+                        proto.backgroundColor = '#eeffff';
+                        target.props.addDocTab(newBox, 'add:right');
+                    },
+                },
+            ],
+        ]);
+
+        const Dependent = new Array<DependentEntry>(
+            {
+                expression: /create (\w+) documents of type (image|nested collection)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    const count = interpretNumber(matches[1]);
+                    const what = matches[2];
+                    const dataDoc = Doc.GetProto(target.props.Document);
+                    const fieldKey = 'data';
+                    if (isNaN(count)) {
+                        return;
+                    }
+                    for (let i = 0; i < count; i++) {
+                        let created: Doc | undefined;
+                        switch (what) {
+                            case 'image':
+                                created = Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
+                                break;
+                            case 'nested collection':
+                                created = Docs.Create.FreeformDocument([], {});
+                                break;
+                        }
+                        created && Doc.AddDocToList(dataDoc, fieldKey, created);
+                    }
+                },
+                restrictTo: [DocumentType.COL],
+            },
+
+            {
+                expression: /view as (freeform|stacking|masonry|schema|tree)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    const mode = matches[1];
+                    mode && (target.props.Document._viewType = mode);
+                },
+                restrictTo: [DocumentType.COL],
+            }
+        );
+    }
+}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0db9eab69..7628beef2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -491,7 +491,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     let actualdW = Math.max(width + dW * scale, 20);
                     let actualdH = Math.max(height + dH * scale, 20);
                     const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen);
-                    console.log(fixedAspect);
                     if (fixedAspect) {
                         if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
                             if (dragRight && modifyNativeDim) {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 60428fc98..3f9db2612 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -6,14 +6,12 @@ import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { Cast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 40b0d285c..849deb04e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -426,28 +426,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
             const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
-            const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
             var alink: Doc | undefined;
-            flattened1.forEach((flat, i) => {
-                const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);
-                this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
-                const sel = flattened[i];
-                tr = tr.addMark(sel.from, sel.to, splitter);
-                tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
-                    if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
-                        alink =
-                            alink ??
-                            (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
-                                DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
-                        newAutoLinks.add(alink);
-                        const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
-                        allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
-                        const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
-                        tr = tr.addMark(pos, pos + node.nodeSize, link);
-                    }
-                });
-                tr = tr.removeMark(sel.from, sel.to, splitter);
+                if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) {
+                    tr = tr.addMark(sel.from, sel.to, splitter);
+                    tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+                        if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+                            alink =
+                                alink ??
+                                (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+                                    DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+                            newAutoLinks.add(alink);
+                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+                            allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
+                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+                            tr = tr.addMark(pos, pos + node.nodeSize, link);
+                        }
+                    });
+                    tr = tr.removeMark(sel.from, sel.to, splitter);
+                }
             });
         }
         return tr;
@@ -841,6 +839,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     recordDictation = () => {
+        console.log('RECORD DICTATIN:');
         DictationManager.Controls.listen({
             interimHandler: this.setDictationContent,
             continuous: { indefinite: false },
@@ -853,26 +852,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
 
     setDictationContent = (value: string) => {
+        console.log('DICTATION CONETNT: ' + value);
         if (this._editorView && this._recordingStart) {
+            console.log('STEP 1');
             if (this._break) {
-                const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
-                this.addDocument(textanchor);
-                const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();
-                link && (Doc.GetProto(link).isDictation = true);
-                if (!link) return;
-                const audioanchor = Cast(link.anchor2, Doc, null);
-                if (!audioanchor) return;
-                audioanchor.backgroundColor = 'tan';
-                const audiotag = this._editorView.state.schema.nodes.audiotag.create({
-                    timeCode: NumCast(audioanchor._timecodeToShow),
-                    audioId: audioanchor[Id],
-                    textId: textanchor[Id],
-                });
-                Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
-                const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
-                const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
-                this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
+                console.log('BREAK');
+                const textanchorFunc = () => {
+                    const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
+                    return this.addDocument(tanch) ? tanch : undefined;
+                };
+                const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
+                if (link) {
+                    Doc.GetProto(link).isDictation = true;
+                    const audioanchor = Cast(link.anchor2, Doc, null);
+                    const textanchor = Cast(link.anchor1, Doc, null);
+                    if (audioanchor) {
+                        audioanchor.backgroundColor = 'tan';
+                        const audiotag = this._editorView.state.schema.nodes.audiotag.create({
+                            timeCode: NumCast(audioanchor._timecodeToShow),
+                            audioId: audioanchor[Id],
+                            textId: textanchor[Id],
+                        });
+                        Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
+                        const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
+                        const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
+                        this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
+                    }
+                }
             }
+            console.log('FINALIziNG');
             const from = this._editorView.state.selection.from;
             this._break = false;
             const tr = this._editorView.state.tr.insertText(value);
@@ -1468,7 +1476,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const editor = this._editorView!;
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
-            let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
+            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);
         }
@@ -1725,7 +1733,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @computed get audioHandle() {
         return (
-            <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}>
+            <div
+                className="formattedTextBox-dictation"
+                onPointerDown={e =>
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        returnFalse,
+                        emptyFunction,
+                        action(e => (this._recording = !this._recording))
+                    )
+                }>
                 <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From f9cded6755ee236d88e21b3b2d4c6da357d059b2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Aug 2022 15:47:35 -0400
Subject: moved 5 sec anno recording button to documentButtonsBar.  made
 recording button appear only when there is one & click to play recording.

---
 src/client/util/CurrentUserUtils.ts                |  3 +-
 src/client/util/DictationManager.ts                |  4 --
 src/client/views/DocumentButtonBar.tsx             | 24 ++++++-
 src/client/views/PropertiesButtons.tsx             |  9 ---
 src/client/views/nodes/DocumentView.scss           |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 82 +++++++++++++---------
 src/client/views/nodes/button/FontIconBox.tsx      |  9 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +---
 8 files changed, 90 insertions(+), 59 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7856c913b..d19874720 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -107,7 +107,7 @@ export class CurrentUserUtils {
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
             const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
-            return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts));
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
         });
 
         const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true};
@@ -633,6 +633,7 @@ export class CurrentUserUtils {
             { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} },
             { title: "Right",  toolTip: "Right align",  btnType: ButtonType.ToggleButton, icon: "align-right",  scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} },
             { title: "NoLink", toolTip: "Auto Link",    btnType: ButtonType.ToggleButton, icon: "link",         scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}},
+            { title: "Dictate",toolTip: "Dictate",      btnType: ButtonType.ToggleButton, icon: "microphone",   scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}},
         ];
     }
 
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 13f036838..0a61f3478 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -152,7 +152,6 @@ export namespace DictationManager {
 
             return new Promise<string>((resolve, reject) => {
                 recognizer.onerror = (e: any) => {
-                    console.log('SPEECH error:', encodeURIComponent);
                     // e is SpeechRecognitionError but where is that defined?
                     if (!(indefinite && e.error === 'no-speech')) {
                         recognizer.stop();
@@ -162,7 +161,6 @@ export namespace DictationManager {
                 };
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
-                    console.log('RESULT: ', e);
                     current = synthesize(e, intra);
                     let matchedTerminator: string | undefined;
                     if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
@@ -175,7 +173,6 @@ export namespace DictationManager {
                 };
 
                 recognizer.onend = (e: Event) => {
-                    console.log('END: ', e);
                     if (!indefinite || isManuallyStopped) {
                         return complete();
                     }
@@ -188,7 +185,6 @@ export namespace DictationManager {
                 };
 
                 const complete = () => {
-                    console.log('COMPLETE:');
                     if (indefinite) {
                         current && sessionResults.push(current);
                         sessionResults.length && resolve(sessionResults.join(inter || interSession));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1d4056759..265df3abc 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
 import { Cast, NumCast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { Docs } from '../documents/Documents';
@@ -334,6 +334,27 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </Tooltip>
         );
     }
+
+    @observable _isRecording = false;
+    @computed
+    get recordButton() {
+        const targetDoc = this.view0?.props.Document;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-tooltip">{'Click to record 5 second annotation'}</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ backgroundColor: this._isRecording ? 'red' : targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+                    onClick={undoBatch(
+                        action(e => {
+                            this._isRecording = true;
+                            this.props.views().map(view => view?.docView?.recordAudioAnnotation(action(() => (this._isRecording = false))));
+                        })
+                    )}>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="microphone" />
+                </div>
+            </Tooltip>
+        );
+    }
     @observable _aliasDown = false;
     onTemplateButton = action((e: React.PointerEvent): void => {
         this._tooltipOpen = false;
@@ -411,6 +432,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
                     </div>
                 ) : null}
+                <div className="documentButtonBar-button">{this.recordButton}</div>
                 {
                     Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
                     /*<div className="documentButtonBar-button">
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 39e7b89c1..80c2c7705 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -68,14 +68,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'thumbtack'
         );
     }
-    @computed get dictationButton() {
-        return this.propertyToggleBtn(
-            'Dictate',
-            '_showAudio',
-            on => `${on ? 'Hide' : 'Show'} dictation/recording controls`,
-            on => 'microphone'
-        );
-    }
     @computed get maskButton() {
         return this.propertyToggleBtn(
             'Mask',
@@ -365,7 +357,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.titleButton)}
                 {toggle(this.captionButton)}
                 {toggle(this.lockButton)}
-                {toggle(this.dictationButton, { display: isNovice ? 'none' : '' })}
                 {toggle(this.onClickButton)}
                 {toggle(this.fitWidthButton)}
                 {toggle(this.freezeThumb)}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c02692bfb..6ea697a2f 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -65,11 +65,13 @@
         width: 25px;
         height: 25;
         position: absolute;
-        top: 10px;
-        left: 10px;
+        top: 0;
+        left: 50%;
         border-radius: 25px;
         background: white;
         opacity: 0.3;
+        pointer-events: all;
+        cursor: default;
 
         svg {
             width: 90% !important;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2524d66dd..4b2bd07ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -52,6 +52,8 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
+import { DictationManager } from '../../util/DictationManager';
+import { Tooltip } from '@material-ui/core';
 const { Howl } = require('howler');
 
 interface Window {
@@ -1005,16 +1007,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
     @computed get contents() {
         TraceMobx();
-        const audioView = !this.layoutDoc._showAudio ? null : (
-            <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter}>
-                <FontAwesomeIcon
-                    className="documentView-audioFont"
-                    style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }}
-                    icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'microphone' : 'file-audio'}
-                    size="sm"
-                />
-            </div>
-        );
+        const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
+        const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+        const audioView =
+            (!this.props.isSelected() && !this._isHovering) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this._mediaState) ? null : (
+                <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
+                    <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
+                        <FontAwesomeIcon className="documentView-audioFont" style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+                    </div>
+                </Tooltip>
+            );
 
         return (
             <div
@@ -1148,26 +1150,29 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     @action
-    onPointerEnter = () => {
+    playAnnotation = () => {
         const self = this;
-        const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']);
-        if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
-            const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
-            anno.data instanceof AudioField &&
-                new Howl({
-                    src: [anno.data.url.href],
-                    format: ['mp3'],
-                    autoplay: true,
-                    loop: false,
-                    volume: 0.5,
-                    onend: function () {
-                        runInAction(() => (self._mediaState = 0));
-                    },
-                });
+        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
+        const anno = audioAnnos.lastElement();
+        if (anno instanceof AudioField && this._mediaState === 0) {
+            new Howl({
+                src: [anno.url.href],
+                format: ['mp3'],
+                autoplay: true,
+                loop: false,
+                volume: 0.5,
+                onend: function () {
+                    runInAction(() => {
+                        console.log('PLAYED');
+                        self._mediaState = 0;
+                    });
+                },
+            });
             this._mediaState = 1;
         }
     };
-    recordAudioAnnotation = () => {
+
+    recordAudioAnnotation = (onEnd?: () => void) => {
         let gumStream: any;
         let recorder: any;
         const self = this;
@@ -1176,18 +1181,30 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 audio: true,
             })
             .then(function (stream) {
+                let audioTextAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+                if (audioTextAnnos) audioTextAnnos.push('');
+                else audioTextAnnos = self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'] = new List<string>(['']);
+                DictationManager.Controls.listen({
+                    interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+                    continuous: { indefinite: false },
+                }).then(results => {
+                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                        DictationManager.Controls.stop();
+                    }
+                    onEnd?.();
+                });
+
                 gumStream = stream;
                 recorder = new MediaRecorder(stream);
                 recorder.ondataavailable = async (e: any) => {
                     const [{ result }] = await Networking.UploadFilesToServer(e.data);
                     if (!(result instanceof Error)) {
-                        const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: 'audio test', _width: 200, _height: 32 });
-                        audioDoc.treeViewExpandedView = 'layout';
-                        const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(Doc));
+                        const audioField = new AudioField(result.accessPaths.agnostic.client);
+                        const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
                         if (audioAnnos === undefined) {
-                            self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioDoc]);
+                            self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioField]);
                         } else {
-                            audioAnnos.push(audioDoc);
+                            audioAnnos.push(audioField);
                         }
                     }
                 };
@@ -1195,6 +1212,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 recorder.start();
                 setTimeout(() => {
                     recorder.stop();
+                    DictationManager.Controls.stop(false);
                     runInAction(() => (self._mediaState = 0));
                     gumStream.getAudioTracks()[0].stop();
                 }, 5000);
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 78ef85ec2..d3b95e25a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
@@ -710,6 +710,13 @@ ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
     }
     if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
 });
+ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) {
+    const textView = RichTextMenu.Instance?.TextView;
+    if (checkResult) {
+        return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent';
+    }
+    if (textView) runInAction(() => (textView._recording = !textView._recording));
+});
 
 ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 849deb04e..929cca1ea 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -771,7 +771,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const uicontrols: ContextMenuProps[] = [];
         !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
-        uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
             uicontrols.push({
@@ -839,7 +838,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     recordDictation = () => {
-        console.log('RECORD DICTATIN:');
         DictationManager.Controls.listen({
             interimHandler: this.setDictationContent,
             continuous: { indefinite: false },
@@ -852,11 +850,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
 
     setDictationContent = (value: string) => {
-        console.log('DICTATION CONETNT: ' + value);
         if (this._editorView && this._recordingStart) {
-            console.log('STEP 1');
             if (this._break) {
-                console.log('BREAK');
                 const textanchorFunc = () => {
                     const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
                     return this.addDocument(tanch) ? tanch : undefined;
@@ -880,7 +875,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     }
                 }
             }
-            console.log('FINALIziNG');
             const from = this._editorView.state.selection.from;
             this._break = false;
             const tr = this._editorView.state.tr.insertText(value);
@@ -1732,7 +1726,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));
 
     @computed get audioHandle() {
-        return (
+        return !this._recording ? null : (
             <div
                 className="formattedTextBox-dictation"
                 onPointerDown={e =>
@@ -1744,7 +1738,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         action(e => (this._recording = !this._recording))
                     )
                 }>
-                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
             </div>
         );
     }
@@ -1898,7 +1892,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     </div>
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
-                    {!this.layoutDoc._showAudio ? null : this.audioHandle}
+                    {this.audioHandle}
                 </div>
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From bca0026764fe554e1066b2f432e749371450d239 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Aug 2022 11:35:00 -0400
Subject: added an audio annotation button to the anchor menu bar.

---
 src/client/views/DocumentButtonBar.tsx             |  12 +-
 src/client/views/MarqueeAnnotator.tsx              |   1 +
 src/client/views/SidebarAnnos.tsx                  |   1 +
 src/client/views/nodes/DocumentView.tsx            |  35 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  11 ++
 src/client/views/pdf/AnchorMenu.tsx                |  14 ++
 src/fields/documentSchemas.ts                      | 182 ++++++++++-----------
 7 files changed, 145 insertions(+), 111 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 265df3abc..81e417fca 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -21,7 +21,7 @@ import './DocumentButtonBar.scss';
 import { Colors } from './global/globalEnums';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
 import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
 import { TemplateMenu } from './TemplateMenu';
@@ -347,7 +347,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     onClick={undoBatch(
                         action(e => {
                             this._isRecording = true;
-                            this.props.views().map(view => view?.docView?.recordAudioAnnotation(action(() => (this._isRecording = false))));
+                            this.props.views().map(
+                                view =>
+                                    view &&
+                                    DocumentViewInternal.recordAudioAnnotation(
+                                        view.dataDoc,
+                                        view.LayoutFieldKey,
+                                        action(() => (this._isRecording = false))
+                                    )
+                            );
                         })
                     )}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="microphone" />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index b01ee5f42..f90ad8bb5 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -65,6 +65,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
 
         AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true);
         AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true));
+        AnchorMenu.Instance.OnAudio = unimplementedFunction;
         AnchorMenu.Instance.Highlight = this.highlight;
         AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations);
         AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1c14c7cd5..90d9c3c43 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -74,6 +74,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
         this.addDocument(target);
         this._stackRef.current?.focusDocument(target);
+        return target;
     };
     makeDocUnfiltered = (doc: Doc) => {
         if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4b2bd07ef..f87581875 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -206,7 +206,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
     _animateScaleTime = 300; // milliseconds;
     @observable _animateScalingTo = 0;
-    @observable _mediaState = 0;
     @observable _pendingDoubleClick = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _downX: number = 0;
@@ -879,7 +878,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
 
             if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
-                !Doc.noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? 'Show' : 'Hide'} Audio Button`, event: action(() => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio)), icon: 'microphone' });
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
@@ -1010,10 +1008,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
         const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
         const audioView =
-            (!this.props.isSelected() && !this._isHovering) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this._mediaState) ? null : (
+            (!this.props.isSelected() && !this._isHovering && this.dataDoc.audioAnnoState !== 2) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this.dataDoc.audioAnnoState) ? null : (
                 <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
                     <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
-                        <FontAwesomeIcon className="documentView-audioFont" style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+                        <FontAwesomeIcon
+                            className="documentView-audioFont"
+                            style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][NumCast(this.dataDoc.audioAnnoState)] }}
+                            icon={!audioAnnosCount ? 'microphone' : 'file-audio'}
+                            size="sm"
+                        />
                     </div>
                 </Tooltip>
             );
@@ -1154,7 +1157,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const self = this;
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos.lastElement();
-        if (anno instanceof AudioField && this._mediaState === 0) {
+        if (anno instanceof AudioField && this.dataDoc.audioAnnoState === 0) {
             new Howl({
                 src: [anno.url.href],
                 format: ['mp3'],
@@ -1163,27 +1166,25 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 volume: 0.5,
                 onend: function () {
                     runInAction(() => {
-                        console.log('PLAYED');
-                        self._mediaState = 0;
+                        self.dataDoc.audioAnnoState = 0;
                     });
                 },
             });
-            this._mediaState = 1;
+            this.dataDoc.audioAnnoState = 1;
         }
     };
 
-    recordAudioAnnotation = (onEnd?: () => void) => {
+    static recordAudioAnnotation(dataDoc: Doc, field: string, onEnd?: () => void) {
         let gumStream: any;
         let recorder: any;
-        const self = this;
         navigator.mediaDevices
             .getUserMedia({
                 audio: true,
             })
             .then(function (stream) {
-                let audioTextAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+                let audioTextAnnos = Cast(dataDoc[field + '-audioAnnotations-text'], listSpec('string'), null);
                 if (audioTextAnnos) audioTextAnnos.push('');
-                else audioTextAnnos = self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'] = new List<string>(['']);
+                else audioTextAnnos = dataDoc[field + '-audioAnnotations-text'] = new List<string>(['']);
                 DictationManager.Controls.listen({
                     interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
                     continuous: { indefinite: false },
@@ -1200,24 +1201,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const [{ result }] = await Networking.UploadFilesToServer(e.data);
                     if (!(result instanceof Error)) {
                         const audioField = new AudioField(result.accessPaths.agnostic.client);
-                        const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
+                        const audioAnnos = Cast(dataDoc[field + '-audioAnnotations'], listSpec(AudioField), null);
                         if (audioAnnos === undefined) {
-                            self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioField]);
+                            dataDoc[field + '-audioAnnotations'] = new List([audioField]);
                         } else {
                             audioAnnos.push(audioField);
                         }
                     }
                 };
-                runInAction(() => (self._mediaState = 2));
+                runInAction(() => (dataDoc.audioAnnoState = 2));
                 recorder.start();
                 setTimeout(() => {
                     recorder.stop();
                     DictationManager.Controls.stop(false);
-                    runInAction(() => (self._mediaState = 0));
+                    runInAction(() => (dataDoc.audioAnnoState = 0));
                     gumStream.getAudioTracks()[0].stop();
                 }, 5000);
             });
-    };
+    }
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
     @computed get innards() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 929cca1ea..223441b3b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -63,6 +63,7 @@ import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { text } from 'body-parser';
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
+import { DocumentViewInternal } from '../DocumentView';
 const translateGoogleApi = require('translate-google-api');
 
 export interface FormattedTextBoxProps {
@@ -249,6 +250,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             !this.layoutDoc.showSidebar && this.toggleSidebar();
             this._sidebarRef.current?.anchorMenuClick(this.getAnchor());
         };
+        AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
+            !this.layoutDoc.showSidebar && this.toggleSidebar();
+            const anchor = this.getAnchor();
+            const target = this._sidebarRef.current?.anchorMenuClick(anchor);
+            if (target) {
+                anchor.followLinkAudio = true;
+                DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
+                target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`);
+            }
+        };
         AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
             this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch);
             return undefined;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 1a1120b6c..ee2ae10a7 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -49,6 +49,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
+    public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
@@ -92,6 +93,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         );
     };
 
+    audioDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+    };
+
     cropDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
@@ -196,6 +201,15 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                               <FontAwesomeIcon icon="comment-alt" size="lg" />
                           </button>
                       </Tooltip>,
+                      AnchorMenu.Instance.OnAudio === unimplementedFunction ? (
+                          <></>
+                      ) : (
+                          <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">{'Click to Record Annotation'}</div>}>
+                              <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
+                                  <FontAwesomeIcon icon="microphone" size="lg" />
+                              </button>
+                          </Tooltip>
+                      ),
                       //NOTE: link popup is currently in progress
                       <Tooltip key="link" title={<div className="dash-tooltip">{'Find document to link to selected text'}</div>}>
                           <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index be39e0709..24b5a359d 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -1,116 +1,114 @@
-import { makeInterface, createSchema, listSpec } from "./Schema";
-import { ScriptField } from "./ScriptField";
-import { Doc } from "./Doc";
-import { DateField } from "./DateField";
-import { SchemaHeaderField } from "./SchemaHeaderField";
+import { makeInterface, createSchema, listSpec } from './Schema';
+import { ScriptField } from './ScriptField';
+import { Doc } from './Doc';
+import { DateField } from './DateField';
+import { SchemaHeaderField } from './SchemaHeaderField';
 
 export const documentSchema = createSchema({
     // content properties
-    type: "string",             // enumerated type of document -- should be template-specific (ie, start with an '_')
-    title: "string",            // document title (can be on either data document or layout)
-    isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field
-    creationDate: DateField,    // when the document was created
-    links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+    type: 'string', // enumerated type of document -- should be template-specific (ie, start with an '_')
+    title: 'string', // document title (can be on either data document or layout)
+    isTemplateForField: 'string', // if specified, it indicates the document is a template that renders the specified field
+    creationDate: DateField, // when the document was created
+    links: listSpec(Doc), // computed (readonly) list of links associated with this document
 
     // "Location" properties in a very general sense
-    _curPage: "number",         // current page of a page based document
-    _currentFrame: "number",    // current frame of a frame based collection (e.g., a progressive slide)
-    lastFrame: "number",        // last frame of a frame based collection (e.g., a progressive slide)
-    activeFrame: "number",      // the active frame of a frame based animated document 
-    _currentTimecode: "number", // current play back time of a temporal document (video / audio)
-    _timecodeToShow: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
-    _timecodeToHIde: "number",  // the time that a document should be hidden
-    markers: listSpec(Doc),     // list of markers for audio / video
-    x: "number",                // x coordinate when in a freeform view 
-    y: "number",                // y coordinate when in a freeform view 
-    z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
-    zIndex: "number",           // zIndex of a document in a freeform view
-    _scrollTop: "number",       // scroll position of a scrollable document (pdf, text, web)
-    lat: "number",
-    lng: "number",
+    _curPage: 'number', // current page of a page based document
+    _currentFrame: 'number', // current frame of a frame based collection (e.g., a progressive slide)
+    lastFrame: 'number', // last frame of a frame based collection (e.g., a progressive slide)
+    activeFrame: 'number', // the active frame of a frame based animated document
+    _currentTimecode: 'number', // current play back time of a temporal document (video / audio)
+    _timecodeToShow: 'number', // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+    _timecodeToHIde: 'number', // the time that a document should be hidden
+    markers: listSpec(Doc), // list of markers for audio / video
+    x: 'number', // x coordinate when in a freeform view
+    y: 'number', // y coordinate when in a freeform view
+    z: 'number', // z "coordinate" - non-zero specifies the overlay layer of a freeformview
+    zIndex: 'number', // zIndex of a document in a freeform view
+    _scrollTop: 'number', // scroll position of a scrollable document (pdf, text, web)
+    lat: 'number',
+    lng: 'number',
 
     // appearance properties on the layout
-    "_backgroundGrid-spacing": "number", // the size of the grid for collection views
-    _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
-    _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
-    _nativeHeight: "number",    // "
-    _width: "number",           // width of document in its container's coordinate system
-    _height: "number",          // "
-    _xPadding: "number",        // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set
-    _yPadding: "number",        // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set
-    _xMargin: "number",         // margin added on left/right of most documents to add separation from their container
-    _yMargin: "number",         // margin added on top/bottom of most documents to add separation from their container
-    _overflow: "string",        // sets overflow behvavior for CollectionFreeForm views
-    _showCaption: "string",     // whether editable caption text is overlayed at the bottom of the document 
-    _showTitle: "string",       // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";".  Use :hover suffix to indicate title should be shown on hover
-    _showAudio: "boolean",      // whether to show the audio record icon on documents
-    _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
-    _columnsFill: "boolean",    // whether documents in a stacking view column should be sized to fill the column
-    _columnsSort: "string",     // how a document should be sorted "ascending", "descending", undefined (none)   
-    _columnsHideIfEmpty: "boolean",   // whether empty stacking view column headings should be hidden
+    '_backgroundGrid-spacing': 'number', // the size of the grid for collection views
+    _autoHeight: 'boolean', // whether the height of the document should be computed automatically based on its contents
+    _nativeWidth: 'number', // native width of document which determines how much document contents are scaled when the document's width is set
+    _nativeHeight: 'number', // "
+    _width: 'number', // width of document in its container's coordinate system
+    _height: 'number', // "
+    _xPadding: 'number', // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set
+    _yPadding: 'number', // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set
+    _xMargin: 'number', // margin added on left/right of most documents to add separation from their container
+    _yMargin: 'number', // margin added on top/bottom of most documents to add separation from their container
+    _overflow: 'string', // sets overflow behvavior for CollectionFreeForm views
+    _showCaption: 'string', // whether editable caption text is overlayed at the bottom of the document
+    _showTitle: 'string', // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";".  Use :hover suffix to indicate title should be shown on hover
+    _pivotField: 'string', // specifies which field key should be used as the timeline/pivot axis
+    _columnsFill: 'boolean', // whether documents in a stacking view column should be sized to fill the column
+    _columnsSort: 'string', // how a document should be sorted "ascending", "descending", undefined (none)
+    _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden
     _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
     _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
-    _fontSize: "string",
-    _fontFamily: "string",
-    _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
+    _fontSize: 'string',
+    _fontFamily: 'string',
+    _sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar
 
     // appearance properties on the data document
-    backgroundColor: "string",  // background color of document
-    borderRounding: "string",   // border radius rounding of document
-    boxShadow: "string",        // the amount of shadow around the perimeter of a document
-    color: "string",            // foreground color of document
-    fitContentsToBox: "boolean",// whether freeform view contents should be zoomed/panned to fill the area of the document view box
-    fontSize: "string",
-    hidden: "boolean",          // whether a document should not be displayed
-    isInkMask: "boolean",       // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
-    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
-    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
-    letterSpacing: "string",
-    opacity: "number",          // opacity of document
-    strokeWidth: "number",
-    strokeBezier: "number",
-    strokeStartMarker: "string",
-    strokeEndMarker: "string",
-    strokeDash: "string",
-    textTransform: "string",
-    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
-    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-    treeViewExpandedViewLock: "boolean", // whether the expanded view can be changed
-    treeViewOpenIsTransient: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
-    treeViewType: "string", // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
+    backgroundColor: 'string', // background color of document
+    borderRounding: 'string', // border radius rounding of document
+    boxShadow: 'string', // the amount of shadow around the perimeter of a document
+    color: 'string', // foreground color of document
+    fitContentsToBox: 'boolean', // whether freeform view contents should be zoomed/panned to fill the area of the document view box
+    fontSize: 'string',
+    hidden: 'boolean', // whether a document should not be displayed
+    isInkMask: 'boolean', // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
+    layout: 'string', // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
+    layoutKey: 'string', // holds the field key for the field that actually holds the current lyoat
+    letterSpacing: 'string',
+    opacity: 'number', // opacity of document
+    strokeWidth: 'number',
+    strokeBezier: 'number',
+    strokeStartMarker: 'string',
+    strokeEndMarker: 'string',
+    strokeDash: 'string',
+    textTransform: 'string',
+    treeViewOpen: 'boolean', //  flag denoting whether the documents sub-tree (contents) is visible or hidden
+    treeViewExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
+    treeViewExpandedViewLock: 'boolean', // whether the expanded view can be changed
+    treeViewOpenIsTransient: 'boolean', // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+    treeViewType: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
 
     // interaction and linking properties
-    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
-    onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
+    ignoreClick: 'boolean', // whether documents ignores input clicks (but does not ignore manipulation and other events)
+    onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
-    onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
-    onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, ) 
-    hideLinkButton: "boolean",  // whether the blue link counter button should be hidden
-    hideAllLinks: "boolean",    // whether all individual blue anchor dots should be hidden
-    linkDisplay: "boolean",     // whether a link connection should be shown between link anchor endpoints.
-    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
-    isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
-    layers: listSpec("string"), // which layers the document is part of
-    _lockedPosition: "boolean",  // whether the document can be moved (dragged)
-    _lockedTransform: "boolean",// whether a freeformview can pan/zoom
-    displayArrow: "boolean", // toggles directed arrows
+    onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+    onDragStart: ScriptField, // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
+    followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, )
+    hideLinkButton: 'boolean', // whether the blue link counter button should be hidden
+    hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
+    linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
+    isInPlaceContainer: 'boolean', // whether the marked object will display addDocTab() calls that target "inPlace" destinations
+    isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked
+    layers: listSpec('string'), // which layers the document is part of
+    _lockedPosition: 'boolean', // whether the document can be moved (dragged)
+    _lockedTransform: 'boolean', // whether a freeformview can pan/zoom
+    displayArrow: 'boolean', // toggles directed arrows
 
     // drag drop properties
-    _stayInCollection: "boolean",// whether document can be dropped into a different collection
-    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
-    childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
-    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
+    _stayInCollection: 'boolean', // whether document can be dropped into a different collection
+    dragFactory: Doc, // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    dropAction: 'string', // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+    targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
+    childDropAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    removeDropProperties: listSpec('string'), // properties that should be removed from the alias/copy/etc of this document when it is dropped
 });
 
-
 export const collectionSchema = createSchema({
     childLayoutTemplate: Doc, // layout template to use to render children of a collecion
-    childLayoutString: "string", //layout string to use to render children of a collection
+    childLayoutString: 'string', //layout string to use to render children of a collection
     childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
-    childDontRegisterViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links 
+    childDontRegisterViews: 'boolean', // whether views made of this document are registered so that they can be found when drawing links
     onChildClick: ScriptField, // script to run for each child when its clicked
     onChildDoubleClick: ScriptField, // script to run for each child when its clicked
     onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
-- 
cgit v1.2.3-70-g09d2


From 0e8afc8e90efdf8037aa233a496e7d7bec75d908 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Aug 2022 13:06:20 -0400
Subject: adjusted tootlip for showing pivot view of dashfieldview.  removed
 scrollbars for autoheight textboxes - metadata views weren't resizing
 properly.

---
 src/client/views/nodes/formattedText/DashFieldView.tsx    | 14 +++++++++-----
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 2 files changed, 10 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8c8b74560..d2f7b5677 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -216,7 +216,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     onPointerDownLabelSpan = (e: any) => {
         setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
-            DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16);
+            DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
         });
     };
 
@@ -253,17 +253,21 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         DashFieldViewMenu.Instance.fadeOut(true);
     };
 
-    public show = (x: number, y: number) => {
+    @observable _fieldKey = '';
+
+    @action
+    public show = (x: number, y: number, fieldKey: string) => {
+        this._fieldKey = fieldKey;
         this.jumpTo(x, y, true);
         const hideMenu = () => {
             this.fadeOut(true);
-            document.removeEventListener('pointerdown', hideMenu);
+            document.removeEventListener('pointerdown', hideMenu, true);
         };
-        document.addEventListener('pointerdown', hideMenu);
+        document.addEventListener('pointerdown', hideMenu, true);
     };
     render() {
         const buttons = [
-            <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
+            <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>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 223441b3b..e027ab0bd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1884,7 +1884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         style={{
                             width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
                             pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
-                            overflow: this.layoutDoc._singleLine ? 'hidden' : undefined,
+                            overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
                         onDrop={this.ondrop}>
-- 
cgit v1.2.3-70-g09d2


From a63f017c213563728f45f2caa7415843f50f3559 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Aug 2022 19:50:33 -0400
Subject: fixed click on menu button for text box with hyperlink to not bring
 up buttonBar.  fixed right click on hyperlink to just bring up button bar,
 but not native context menu.  fixe stacking view child pointer events so that
 sidebar documents in lightbox are editable.

---
 src/client/views/DocumentButtonBar.tsx                    |  7 +------
 src/client/views/collections/CollectionStackingView.tsx   |  5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 +++++-----
 3 files changed, 9 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 81e417fca..1f8550ad6 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -274,12 +274,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get menuButton() {
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? null : (
-            <Tooltip
-                title={
-                    <>
-                        <div className="dash-tooltip">{`Open Context Menu`}</div>
-                    </>
-                }>
+            <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
                 <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
                 </div>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ef68cadd7..d4efef47a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -300,9 +300,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+    isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
 
-    isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive));
+    isChildContentActive = () =>
+        this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
     getDisplayDoc(doc: Doc, width: () => number) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e027ab0bd..f61533619 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -703,9 +703,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
         const editor = this._editorView!;
         const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
-        let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
+        let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
         while (target && !target.dataset?.targethrefs) target = target.parentElement;
-        if (target) {
+        if (target && !(e.nativeEvent as any).dash) {
             const hrefs = (target.dataset?.targethrefs as string)
                 ?.trim()
                 .split(' ')
@@ -724,6 +724,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
                     })
                 );
+            e.preventDefault();
             e.stopPropagation();
             return;
         }
@@ -1474,10 +1475,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         document.removeEventListener('pointermove', this.onSelectMove);
     };
     onPointerUp = (e: React.PointerEvent): void => {
-        if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu();
+        if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu();
         if (!this._downEvent) return;
         this._downEvent = false;
-        if (this.props.isContentActive(true)) {
+        if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) {
             const editor = this._editorView!;
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
@@ -1795,7 +1796,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 />
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
-                    //@ts-ignore
                     <ComponentTag
                         {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                         NativeWidth={returnZero}
-- 
cgit v1.2.3-70-g09d2


From 440f5c296a599d5c3f9ed781b58c5b00da6edb5a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Aug 2022 17:18:10 -0400
Subject: added animation of background color, and streamlined/extended a
 variety of props-related things.

---
 src/client/util/CurrentUserUtils.ts                |  6 +--
 src/client/views/DocComponent.tsx                  | 14 -------
 src/client/views/StyleProvider.tsx                 |  9 +++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++++++--------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 30 ++++++++++++---
 src/client/views/nodes/FieldView.tsx               | 11 ++++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 45 ++++++++++------------
 src/fields/Doc.ts                                  | 14 +++++++
 src/fields/ScriptField.ts                          | 12 +++++-
 10 files changed, 102 insertions(+), 74 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 20c57c617..17d58595c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -235,9 +235,9 @@ export class CurrentUserUtils {
             const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", 
                 layout:
                 "<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
-                `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'}  height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
-                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'}  dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' background='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
-                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' background='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
+                `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
+                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' backgroundColor='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
+                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
                 "</HTMLdiv>"
             }, "header");
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 280ca8a8c..886dd974b 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -132,20 +132,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
-        styleFromLayoutString = (scale: number) => {
-            const style: { [key: string]: any } = {};
-            const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
-            const replacer = (match: any, expr: string, offset: any, string: any) => {
-                // bcz: this executes a script to convert a property expression string:  { script }  into a value
-                return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? '';
-            };
-            divKeys.map((prop: string) => {
-                const p = (this.props as any)[prop];
-                typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
-            });
-            return style;
-        };
-
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
         @computed public get annotationKey() {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3bd4f5152..c0ba170c6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -40,7 +40,8 @@ export enum StyleProp {
     JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
     BorderPath = 'customBorder', // border path for document view
     FontSize = 'fontSize', // size of text font
-    FontFamily = 'fontFamily', // size of text font
+    FontFamily = 'fontFamily', // font family of text
+    FontWeight = 'fontWeight', // font weight of text
 }
 
 function darkScheme() {
@@ -117,9 +118,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.HideLinkButton:
             return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.FontSize:
-            return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
+            return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._fontSize, StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily:
-            return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+            return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+        case StyleProp.FontWeight:
+            return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.ShowTitle:
             return (
                 (doc &&
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b210e7d9a..d84717b95 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -34,7 +34,7 @@ export interface PoolData {
     zIndex?: number;
     width?: number;
     height?: number;
-    color?: string;
+    backgroundColor?: string;
     opacity?: number;
     transition?: string;
     highlight?: boolean;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1d518076a..a807ba4ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -115,7 +115,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
-    @observable _keyframeEditing = false;
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
@@ -185,8 +184,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
         }
     };
-    @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
-    getKeyFrameEditing = () => this._keyframeEditing;
     onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
     onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
@@ -272,15 +269,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
         zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
-        const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+        const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
-                vals.x = x + (vals.x || 0) - dropPos[0];
-                vals.y = y + (vals.y || 0) - dropPos[1];
+                vals.x = x + NumCast(vals.x) - dropPos[0];
+                vals.y = y + NumCast(vals.y) - dropPos[1];
                 vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined;
                 CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
             } else {
@@ -1317,22 +1314,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     });
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
-        const layoutDoc = Doc.Layout(params.pair.layout);
-        const { z, color, zIndex } = params.pair.layout;
-        const { x, y, opacity } =
-            this.Document._currentFrame === undefined
-                ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.childOpacity ? this.props.childOpacity() : this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
-                : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
+        const curFrame = NumCast(this.Document._currentFrame);
+        const childDoc = params.pair.layout;
+        const childDocLayout = Doc.Layout(childDoc);
+        const { z, zIndex } = childDoc;
+        const { backgroundColor } = curFrame === undefined ? { backgroundColor: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
+        const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
         return {
             x: NumCast(x),
             y: NumCast(y),
             z: Cast(z, 'number'),
-            color: StrCast(color),
+            backgroundColor: Cast(backgroundColor, 'string') ?? this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.BackgroundColor),
+            opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
-            transition: StrCast(layoutDoc.dataTransition),
-            opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
-            width: Cast(layoutDoc._width, 'number'),
-            height: Cast(layoutDoc._height, 'number'),
+            width: Cast(childDocLayout._width, 'number'),
+            height: Cast(childDocLayout._height, 'number'),
+            transition: StrCast(childDocLayout.dataTransition),
             pair: params.pair,
             replica: '',
         };
@@ -1447,7 +1444,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             for (const entry of array) {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+                if (!lastPos || newPos.backgroundColor !== lastPos.backgroundColor || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
                 if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e19e2d525..7dc8a7fbb 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -18,7 +18,7 @@ import { DocumentView, DocumentViewProps } from './DocumentView';
 import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
     sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
@@ -32,6 +32,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
     public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
+    public static animStringFields = ['backgroundColor']; // fields that are configured to be animatable using animation frames
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
     get displayName() {
@@ -41,16 +42,19 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
     get X() {
-        return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
+        return this.dataProvider?.x ?? NumCast(this.Document.x);
     }
     get Y() {
-        return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y);
+        return this.dataProvider?.y ?? NumCast(this.Document.y);
     }
     get ZInd() {
-        return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex);
+        return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
     }
     get Opacity() {
-        return this.dataProvider ? this.dataProvider.opacity : undefined;
+        return this.dataProvider?.opacity;
+    }
+    get BackgroundColor() {
+        return this.dataProvider?.backgroundColor;
     }
     get Highlight() {
         return this.dataProvider?.highlight;
@@ -67,6 +71,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
+        if (property === StyleProp.BackgroundColor) {
+            return this.BackgroundColor; // only change the opacity for this specific document, not its children
+        }
         return this.props.styleProvider?.(doc, props, property);
     };
 
@@ -77,6 +84,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }, {} as { [val: string]: Opt<number> });
     }
 
+    public static getStringValues(doc: Doc, time: number) {
+        return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
+            p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+            return p;
+        }, {} as { [val: string]: Opt<string> });
+    }
+
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
@@ -95,6 +109,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null);
                     findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
                 });
+                CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+                    const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+                    findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+                });
             })
         );
         setTimeout(
@@ -142,6 +160,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
             CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode)));
+            CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
             doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
             doc.dataTransition = 'inherit';
         });
@@ -189,7 +208,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index dd2c13391..77aaa4441 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -26,16 +26,21 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
     onBrowseClick?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    pointerEvents?: () => Opt<string>;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
-    pointerEvents?: () => Opt<string>;
+    // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
+    // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
+    backgroundColor?: string;
+    color?: string;
     fontSize?: number;
     height?: number;
     width?: number;
-    background?: string;
-    color?: string;
     xPadding?: number;
     yPadding?: number;
+    noSidebar?: boolean;
+    dontScale?: boolean;
+    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
 }
 
 @observer
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f61533619..81ac45521 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -44,6 +45,7 @@ import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
+import { DocumentViewInternal } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -61,25 +63,14 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { text } from 'body-parser';
-import { CollectionTreeView } from '../../collections/CollectionTreeView';
-import { DocumentViewInternal } from '../DocumentView';
 const translateGoogleApi = require('translate-google-api');
 
-export interface FormattedTextBoxProps {
-    makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text
-    xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
-    yPadding?: number;
-    noSidebar?: boolean;
-    dontScale?: boolean;
-    dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
-}
 export const GoogleRef = 'googleDocId';
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -1843,18 +1834,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
         const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
         const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
-        const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
-        return styleFromString?.height === '0px' ? null : (
+        const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        return styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox-cont"
                 onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                 style={{
-                    transform: this.props.dontScale ? undefined : `scale(${scale})`,
-                    transformOrigin: this.props.dontScale ? undefined : 'top left',
-                    width: this.props.dontScale ? undefined : `${100 / scale}%`,
-                    height: this.props.dontScale ? undefined : `${100 / scale}%`,
+                    ...(this.props.dontScale
+                        ? {}
+                        : {
+                              transform: `scale(${scale})`,
+                              transformOrigin: 'top left',
+                              width: `${100 / scale}%`,
+                              height: `${100 / scale}%`,
+                          }),
+                    transition: 'inherit',
                     // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
-                    ...styleFromString,
+                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+                    fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
+                    fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
+                    fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+                    ...styleFromLayoutString,
                 }}>
                 <div
                     className={`formattedTextBox-cont`}
@@ -1862,11 +1862,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     style={{
                         overflow: this.autoHeight ? 'hidden' : undefined,
                         height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
-                        background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
-                        color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-                        fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
-                        fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
-                        fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
                         pointerEvents: interactive ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8d56ebf8c..612fc7fb8 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1389,6 +1389,20 @@ export namespace Doc {
         return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1;
     }
 
+    export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) {
+        const style: { [key: string]: any } = {};
+        const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+        const replacer = (match: any, expr: string, offset: any, string: any) => {
+            // bcz: this executes a script to convert a property expression string:  { script }  into a value
+            return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: rootDoc, this: layoutDoc, scale }).result?.toString() ?? '';
+        };
+        divKeys.map((prop: string) => {
+            const p = props[prop];
+            typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+        });
+        return style;
+    }
+
     // prettier-ignore
     export function toIcon(doc?: Doc, isOpen?: boolean) {
         switch (StrCast(doc?.type)) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 68fb45987..0fd992d3b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -9,7 +9,7 @@ import { Doc, Field, Opt } from './Doc';
 import { Copy, Id, ToScriptString, ToString } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
-import { Cast, NumCast } from './Types';
+import { Cast, NumCast, StrCast } from './Types';
 import { Plugins } from './util';
 
 function optional(propSchema: PropSchema) {
@@ -199,6 +199,16 @@ export class ComputedField extends ScriptField {
         const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
         return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
     }
+    public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
+        if (!doc[`${fieldKey}-indexed`]) {
+            const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
+            flist[curTimecode] = StrCast(doc[fieldKey]);
+            doc[`${fieldKey}-indexed`] = flist;
+        }
+        const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
+        const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
+        return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+    }
 }
 export namespace ComputedField {
     let useComputed = true;
-- 
cgit v1.2.3-70-g09d2


From 2c5942d76ad6e9b5874b98658b7c5af59cdfa367 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Sep 2022 14:38:38 -0400
Subject: fixed font menu settings to be accurate of current selection.

---
 src/client/documents/Documents.ts                  | 16 ++++--
 src/client/util/CurrentUserUtils.ts                |  4 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 36 ++++---------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 62 +++++++++-------------
 5 files changed, 49 insertions(+), 73 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7111cb233..57a24b304 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1386,11 +1386,17 @@ export namespace DocUtils {
         scripts &&
             Object.keys(scripts).map(key => {
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) {
-                    doc[key] = ScriptField.MakeScript(
-                        scripts[key],
-                        { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name },
-                        { _readOnly_: true }
-                    );
+                    doc[key] = ScriptField.MakeScript(scripts[key], {
+                        dragData: DragManager.DocumentDragData.name,
+                        value: 'any',
+                        _readOnly_: 'boolean',
+                        scriptContext: 'any',
+                        thisContainer: Doc.name,
+                        documentView: Doc.name,
+                        heading: Doc.name,
+                        checked: 'boolean',
+                        containingTreeView: Doc.name,
+                    });
                 }
             });
         funcs &&
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 25852c6ce..9d3f19e50 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -623,8 +623,8 @@ export class CurrentUserUtils {
         return [
             { title: "Font",    toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,               ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            { title: "Size",    toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton,               ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
-            { title: "Color",   toolTip: "Font color",           btnType: ButtonType.ColorButton,  icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}},
+            { title: "Size",    toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton,               ignoreClick: true, scripts: {script:  '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
+            { title: "Color",   toolTip: "Font color",           btnType: ButtonType.ColorButton,  icon: "font", ignoreClick: true, scripts: {script:  '{ return setFontColor(value, _readOnly_);}'}},
             { title: "Bold",    toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",                    scripts: {onClick: '{ return toggleBold(_readOnly_); }'} },
             { title: "Italic",  toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",                  scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} },
             { title: "Under",   toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",               scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} },
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index c72b5ca9b..4a6099fb3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -576,27 +576,19 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) {
-    SelectionManager.Docs().map(doc => (doc._fontFamily = font));
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    if (checkResult) {
-        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-    }
-    if (editorView) RichTextMenu.Instance.setFontFamily(font);
-    else Doc.UserDoc().fontFamily = font;
+    if (checkResult) return RichTextMenu.Instance?.fontFamily;
+    font && RichTextMenu.Instance.setFontFamily(font);
 });
 
 ScriptingGlobals.add(function getActiveTextInfo(info: 'family' | 'size' | 'color' | 'highlight') {
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
     const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
+    // prettier-ignore
     switch (info) {
-        case 'family':
-            return style?.activeFamilies[0];
-        case 'size':
-            return style?.activeSizes[0];
-        case 'color':
-            return style?.activeColors[0];
-        case 'highlight':
-            return style?.activeHighlights[0];
+        case 'family':    return style?.activeFamilies[0];
+        case 'size':      return style?.activeSizes[0];
+        case 'color':     return style?.activeColors[0];
+        case 'highlight': return style?.activeHighlights[0];
     }
 });
 
@@ -621,14 +613,8 @@ ScriptingGlobals.add(function setBulletList(mapStyle: 'bullet' | 'decimal', chec
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-
-    if (checkResult) {
-        return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor;
-    }
-
-    if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
-    else Doc.UserDoc().fontColor = color;
+    if (checkResult) return RichTextMenu.Instance.fontColor;
+    color && RichTextMenu.Instance.setColor(color);
 });
 
 // toggle: Set overlay status of selected document
@@ -650,14 +636,12 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
         return RichTextMenu.Instance?.fontSize.replace('px', '');
     }
     if (typeof size === 'number') size = size.toString();
     if (size && Number(size).toString() === size) size += 'px';
-    if (editorView) RichTextMenu.Instance.setFontSize(size);
-    else Doc.UserDoc()._fontSize = size;
+    RichTextMenu.Instance.setFontSize(size);
 });
 ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 81ac45521..314696251 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1387,10 +1387,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         selectOnLoad && this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-        if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+        if (this._editorView) {
             this._editorView.state.storedMarks = [
                 ...(this._editorView.state.storedMarks ?? []),
-                schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+                ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []),
                 ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
                 ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
                 ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 0cbe60c0c..42a204e1d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -64,6 +64,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         super(props);
         runInAction(() => {
             RichTextMenu.Instance = this;
+            this.updateMenu(undefined, undefined, props);
             this._canFade = false;
             this.Pinned = true;
         });
@@ -103,13 +104,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             return;
         }
         this.view = view;
-        if (!view || !view.hasFocus()) {
-            return;
-        }
         props && (this.editorProps = props);
 
         // Don't do anything if the document/selection didn't change
-        if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
+        if (view && view.hasFocus()) {
+            if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
+        }
 
         // update active marks
         const activeMarks = this.getActiveMarksOnSelection();
@@ -124,9 +124,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
         this.activeListType = this.getActiveListStyle();
         this._activeAlignment = this.getActiveAlignment();
-        this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
-        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
-        this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...';
+        this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
+        this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
         this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
 
         // update link in current selection
@@ -279,28 +279,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this._superscriptActive = false;
 
         activeMarks.forEach(mark => {
+            // prettier-ignore
             switch (mark.name) {
-                case 'noAutoLinkAnchor':
-                    this._noLinkActive = true;
-                    break;
-                case 'strong':
-                    this._boldActive = true;
-                    break;
-                case 'em':
-                    this._italicsActive = true;
-                    break;
-                case 'underline':
-                    this._underlineActive = true;
-                    break;
-                case 'strikethrough':
-                    this._strikethroughActive = true;
-                    break;
-                case 'subscript':
-                    this._subscriptActive = true;
-                    break;
-                case 'superscript':
-                    this._superscriptActive = true;
-                    break;
+                case 'noAutoLinkAnchor': this._noLinkActive = true; break;
+                case 'strong':           this._boldActive = true; break;
+                case 'em':               this._italicsActive = true; break;
+                case 'underline':        this._underlineActive = true; break;
+                case 'strikethrough':    this._strikethroughActive = true; break;
+                case 'subscript':        this._subscriptActive = true; break;
+                case 'superscript':      this._superscriptActive = true; break;
             }
         });
     }
@@ -342,14 +329,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
                 this.TextView.dataDoc.fontSize = fontSize;
                 this.view.focus();
-                this.updateMenu(this.view, undefined, this.props);
             } else {
                 const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
                 this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
                 this.view.focus();
-                this.updateMenu(this.view, undefined, this.props);
             }
-        }
+        } else Doc.UserDoc()._fontSize = fontSize;
+        this.updateMenu(this.view, undefined, this.props);
     };
 
     setFontFamily = (family: string) => {
@@ -357,8 +343,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
             this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
             this.view.focus();
-            this.updateMenu(this.view, undefined, this.props);
-        }
+        } else Doc.UserDoc()._fontFamily = family;
+        this.updateMenu(this.view, undefined, this.props);
     };
 
     setHighlight(color: String, view: EditorView, dispatch: any) {
@@ -368,13 +354,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.setMark(highlightMark, view.state, dispatch, false);
     }
 
-    setColor(color: String, view: EditorView, dispatch: any) {
+    setColor(color: string) {
         if (this.view) {
-            const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color });
+            const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
             this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
-            view.focus();
-            this.updateMenu(this.view, undefined, this.props);
-        }
+            this.view.focus();
+        } else Doc.UserDoc().fontColor = color;
+        this.updateMenu(this.view, undefined, this.props);
     }
 
     // TODO: remove doesn't work
-- 
cgit v1.2.3-70-g09d2


From 51a4c0978cdc65a4a16bc9f03c7e4eff551769af Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 12 Sep 2022 13:12:39 -0400
Subject: fixed autosizing text with header tags.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 314696251..5cb805f80 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1697,7 +1697,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children) {
-            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins);
+            const proseHeight = !this.ProseRef
+                ? 0
+                : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')) + Number(getComputedStyle(child).marginTop.replace('px', '')) + Number(getComputedStyle(child).marginBottom.replace('px', '')), margins);
             const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
             if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-- 
cgit v1.2.3-70-g09d2

