From 0546ecf205b7d2b76f341a7157beebf95fb888a8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 1 Aug 2021 22:43:46 -0400
Subject: made url server references relative.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 140d39929..f7e9ee028 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -371,7 +371,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
                 const anchor = Docs.Create.TextanchorDocument();
                 const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
-                const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+                const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }];
                 const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location });
                 tr = tr.addMark(flattened[i].from, flattened[i].to, link);
             });
@@ -705,7 +705,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
             if (sel.from !== sel.to) {
                 const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
-                const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+                const href = targetHref ?? Doc.localServerPath(anchor);
                 if (anchor !== anchorDoc) this.addDocument(anchor);
                 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.splitter.name)) {
@@ -1042,7 +1042,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
-            const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
+            const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
             const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
             marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
             return node.mark(marks);
-- 
cgit v1.2.3-70-g09d2


From 6d9c65bf95091b0d5ee7e3097995fc9e2ead4415 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 5 Aug 2021 11:21:04 -0400
Subject: improved opening sidebar when following a link to a sidebar
 annotation.  Still needs work when link is from outside of the sidebar
 document and on first click.

---
 src/client/views/SidebarAnnos.tsx                  |  5 +++--
 src/client/views/nodes/PDFBox.tsx                  | 26 +++++++++++++++-------
 src/client/views/nodes/WebBox.tsx                  | 24 ++++++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++++++++++++-----
 4 files changed, 53 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 7b11723c3..f1d168a22 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -23,6 +23,7 @@ interface ExtraProps {
     layoutDoc: Doc;
     rootDoc: Doc;
     dataDoc: Doc;
+    showSidebar: boolean;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     ScreenToLocalTransform: () => Transform;
     sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean;
@@ -75,7 +76,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     get sidebarKey() { return this.props.fieldKey + "-sidebar"; }
     filtersHeight = () => 38;
     screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1);
-    panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
+    panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
     panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
     addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
@@ -103,7 +104,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 {user}
             </div>;
         };
-        return !this.props.layoutDoc._showSidebar ? (null) :
+        return !this.props.showSidebar ? (null) :
             <div style={{
                 position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: 0, right: 0,
                 background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 0b451e2b4..fc8fa4eef 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,10 +3,10 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
 import { documentSchema } from '../../../fields/documentSchemas';
 import { makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
 import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
@@ -66,6 +66,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     scrollFocus = (doc: Doc, smooth: boolean) => {
+        if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+            this.toggleSidebar(!smooth);
+        }
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         this._initialScrollTarget = doc;
         return this._pdfViewer?.scrollFocus(doc, smooth);
@@ -141,15 +144,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
             }
             return false;
-        }, emptyFunction, this.toggleSidebar);
+        }, emptyFunction, () => this.toggleSidebar());
     }
-    toggleSidebar = action(() => {
+    toggleSidebar = action((preview: boolean = false) => {
         const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
         const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-        this.layoutDoc.nativeWidth = nativeWidth * ratio;
-        this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
-        this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+        if (preview) this._showSidebar = true;
+        else {
+            this.layoutDoc.nativeWidth = nativeWidth * ratio;
+            this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+            this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+        }
     });
     settingsPanel() {
         const pageBtns = <>
@@ -202,7 +208,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 </button>
             </div>;
     }
-    sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+    sidebarWidth = () => !this.SidebarShown ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
@@ -223,6 +229,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+    @observable _showSidebar = false;
+    @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+
 
     @computed get renderPdfView() {
         TraceMobx();
@@ -255,6 +264,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 rootDoc={this.rootDoc}
                 layoutDoc={this.layoutDoc}
                 dataDoc={this.dataDoc}
+                showSidebar={this.SidebarShown}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 sidebarAddDocument={this.sidebarAddDocument}
                 moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f5b1f96f2..bb2c09fdf 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -160,6 +160,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
 
     scrollFocus = (doc: Doc, smooth: boolean) => {
+        if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+            this.toggleSidebar(!smooth);
+        }
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         if (doc !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
@@ -449,17 +452,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
             }
             return false;
-        }, emptyFunction, this.toggleSidebar);
+        }, emptyFunction, () => this.toggleSidebar());
     }
-    toggleSidebar = action(() => {
+    toggleSidebar = action((preview: boolean = false) => {
         const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
         const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-        this.layoutDoc.nativeWidth = nativeWidth * ratio;
-        this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
-        this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+        if (preview) this._showSidebar;
+        else {
+            this.layoutDoc.nativeWidth = nativeWidth * ratio;
+            this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+            this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+        }
     });
-    sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+    sidebarWidth = () => !this.SidebarShown ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
 
     @computed get content() {
         return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
@@ -475,7 +481,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
             }
         </div>;
+
     }
+    @observable _showSidebar = false;
+    @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
 
     showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
@@ -548,10 +557,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 </div >
                 <SidebarAnnos ref={this._sidebarRef}
                     {...this.props}
-                    fieldKey={this.annotationKey}
+                    fieldKey={this.fieldKey}
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
+                    showSidebar={this.SidebarShown}
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f7e9ee028..d308345ec 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -120,7 +120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public get EditorView() { return this._editorView; }
     public get SidebarKey() { return this.fieldKey + "-sidebar"; }
 
-    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
+    @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
     @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
     @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
@@ -544,11 +544,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    @observable _showSidebar = false;
+    @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+
     @action
-    toggleSidebar = () => {
+    toggleSidebar = (preview: boolean = false) => {
         const prevWidth = this.sidebarWidth();
-        this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
-        this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+        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);
     }
     sidebarDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
@@ -726,6 +731,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+        if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+            this.toggleSidebar(!smooth);
+        }
         const textAnchorId = textAnchor[Id];
         const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
             const nodes: Node[] = [];
@@ -1468,10 +1476,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             return ComponentTag === CollectionStackingView ?
                 <SidebarAnnos ref={this._sidebarRef}
                     {...this.props}
-                    fieldKey={this.annotationKey}
+                    fieldKey={this.fieldKey}
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
+                    showSidebar={this.SidebarShown}
                     PanelWidth={this.sidebarWidth}
                     setHeight={this.setSidebarHeight}
                     sidebarAddDocument={this.sidebarAddDocument}
@@ -1569,7 +1578,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             }}
                         />
                     </div>
-                    {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
+                    {(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.layoutDoc._showAudio ? (null) : this.audioHandle}
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 5550d58f16a0df090cde5a3605f6307b17e70758 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 5 Aug 2021 17:22:42 -0400
Subject: fixed so that links to sidebar annos show the sidebar in a
 LinkPreview

---
 src/client/views/SidebarAnnos.tsx                  |  3 +-
 src/client/views/collections/CollectionView.tsx    |  5 +-
 src/client/views/nodes/PDFBox.tsx                  | 68 ++++++++++++++--------
 src/client/views/nodes/WebBox.tsx                  | 35 ++++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 +
 src/client/views/pdf/PDFViewer.tsx                 |  2 -
 6 files changed, 70 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index f1d168a22..1f9763d18 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -24,6 +24,7 @@ interface ExtraProps {
     rootDoc: Doc;
     dataDoc: Doc;
     showSidebar: boolean;
+    nativeWidth: number;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     ScreenToLocalTransform: () => Transform;
     sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean;
@@ -76,7 +77,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     get sidebarKey() { return this.props.fieldKey + "-sidebar"; }
     filtersHeight = () => 38;
     screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1);
-    panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
+    panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (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);
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2718cbbbf..2ae06d2f4 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -245,10 +245,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         TraceMobx();
         const props: SubCollectionViewProps = {
             ...this.props,
-            SetSubView: action((subView: any) => {
-                console.log("Setting sub", subView);
-                return this._subView = subView
-            }),
+            SetSubView: action((subView: any) => this._subView = subView),
             addDocument: this.addDocument,
             moveDocument: this.moveDocument,
             removeDocument: this.removeDocument,
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fc8fa4eef..b1f2070f8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -9,7 +9,7 @@ import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch } from '../../util/UndoManager';
@@ -31,6 +31,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, PdfDocument>(PdfDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
+    public static openSidebarWidth = 250;
     private _searchString: string = "";
     private _initialScrollTarget: Opt<Doc>;
     private _pdfViewer: PDFViewer | undefined;
@@ -146,11 +147,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return false;
         }, emptyFunction, () => this.toggleSidebar());
     }
+    @observable _previewNativeWidth: Opt<number> = undefined;
+    @observable _previewWidth: Opt<number> = undefined;
     toggleSidebar = action((preview: boolean = false) => {
         const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
-        const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+        const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-        if (preview) this._showSidebar = true;
+        if (preview) {
+            this._previewNativeWidth = nativeWidth * ratio;
+            this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+            this._showSidebar = true;
+        }
         else {
             this.layoutDoc.nativeWidth = nativeWidth * ratio;
             this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
@@ -208,7 +215,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 </button>
             </div>;
     }
-    sidebarWidth = () => !this.SidebarShown ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+    sidebarWidth = () => !this.SidebarShown ? 0 :
+        this._previewWidth ? PDFBox.openSidebarWidth :
+            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
@@ -232,38 +241,51 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable _showSidebar = false;
     @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
 
-
+    contentScaling = () => {
+        return 1;
+    }
     @computed get renderPdfView() {
         TraceMobx();
+        const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+        const scale = previewScale * (this.props.scaling?.() || 1);
         return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}
             style={{
                 height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ?
                     NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined
             }}>
             <div className="pdfBox-background" />
-            <PDFViewer {...this.props}
-                rootDoc={this.rootDoc}
-                layoutDoc={this.layoutDoc}
-                dataDoc={this.dataDoc}
-                pdf={this._pdf!}
-                url={this.pdfUrl!.url.pathname}
-                isContentActive={this.isContentActive}
-                anchorMenuClick={this.anchorMenuClick}
-                loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
-                setPdfViewer={this.setPdfViewer}
-                addDocument={this.addDocument}
-                moveDocument={this.moveDocument}
-                removeDocument={this.removeDocument}
-                whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                startupLive={true}
-                ContentScaling={this.props.scaling}
-                sidebarWidth={this.sidebarWidth}
-            />
+            <div style={{
+                width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
+                height: `${100 / scale}%`,
+                transform: `scale(${scale})`,
+                position: "absolute",
+                transformOrigin: "top left",
+                top: 0
+            }}>
+                <PDFViewer {...this.props}
+                    rootDoc={this.rootDoc}
+                    layoutDoc={this.layoutDoc}
+                    dataDoc={this.dataDoc}
+                    pdf={this._pdf!}
+                    url={this.pdfUrl!.url.pathname}
+                    isContentActive={this.isContentActive}
+                    anchorMenuClick={this.anchorMenuClick}
+                    loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
+                    setPdfViewer={this.setPdfViewer}
+                    addDocument={this.addDocument}
+                    moveDocument={this.moveDocument}
+                    removeDocument={this.removeDocument}
+                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                    startupLive={true}
+                    ContentScaling={returnOne}
+                />
+            </div>
             <SidebarAnnos ref={this._sidebarRef}
                 {...this.props}
                 rootDoc={this.rootDoc}
                 layoutDoc={this.layoutDoc}
                 dataDoc={this.dataDoc}
+                nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
                 showSidebar={this.SidebarShown}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 sidebarAddDocument={this.sidebarAddDocument}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index bb2c09fdf..d2c806df6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -43,6 +43,7 @@ const WebDocument = makeInterface(documentSchema);
 @observer
 export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, WebDocument>(WebDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
+    public static openSidebarWidth = 250;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -454,18 +455,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return false;
         }, emptyFunction, () => this.toggleSidebar());
     }
+    @observable _previewNativeWidth: Opt<number> = undefined;
+    @observable _previewWidth: Opt<number> = undefined;
     toggleSidebar = action((preview: boolean = false) => {
         const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
-        const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+        const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-        if (preview) this._showSidebar;
+        if (preview) {
+            this._previewNativeWidth = nativeWidth * ratio;
+            this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+            this._showSidebar = true;
+        }
         else {
             this.layoutDoc.nativeWidth = nativeWidth * ratio;
             this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
             this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
         }
     });
-    sidebarWidth = () => !this.SidebarShown ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+    sidebarWidth = () => !this.SidebarShown ? 0 :
+        this._previewWidth ? WebBox.openSidebarWidth :
+            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
+            NumCast(this.layoutDoc.nativeWidth);
 
     @computed get content() {
         return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
@@ -493,29 +503,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     render() {
-        const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
-        const scale = this.props.scaling?.() || 1;
+        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+        const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+        const scale = previewScale * (this.props.scaling?.() || 1);
         return (
             <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.isContentActive() ? "all" : this.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
-                <div className={`webBox-container`}
-                    style={{ pointerEvents: inactiveLayer ? "none" : undefined }}
-                    onContextMenu={this.specificContextMenu}>
+                <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
                     <base target="_blank" />
                     <div className={"webBox-outerContent"} ref={this._outerRef}
                         style={{
-                            width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale}px)`,
+                            width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
                             height: `${100 / scale}%`,
                             transform: `scale(${scale})`,
-                            pointerEvents: inactiveLayer ? "none" : undefined
+                            pointerEvents
                         }}
                         onWheel={e => { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe
                         onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                         onPointerDown={this.onMarqueeDown}
                     >
-                        <div className={"webBox-innerContent"} style={{
-                            height: NumCast(this.scrollHeight, 50),
-                            pointerEvents: inactiveLayer ? "none" : undefined
-                        }}>
+                        <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}>
                             {this.content}
                             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                                 renderDepth={this.props.renderDepth + 1}
@@ -561,6 +567,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
+                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
                     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 d308345ec..e5c36ea71 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1480,6 +1480,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
+                    nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                     showSidebar={this.SidebarShown}
                     PanelWidth={this.sidebarWidth}
                     setHeight={this.setSidebarHeight}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e8c7a4ab0..e7911e8f8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -46,7 +46,6 @@ interface IViewerProps extends FieldViewProps {
     loaded?: (nw: number, nh: number, np: number) => void;
     setPdfViewer: (view: PDFViewer) => void;
     ContentScaling?: () => number;
-    sidebarWidth: () => number;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
 }
 
@@ -550,7 +549,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 style={{
                     overflowX: this._zoomed !== 1 ? "scroll" : undefined,
-                    width: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeWidth(this.props.Document) - this.props.sidebarWidth() / this.contentScaling : `calc(${100 / this.contentScaling}% - ${this.props.sidebarWidth() / this.contentScaling}px)`,
                     height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
                     transform: `scale(${this.contentScaling})`
                 }}  >
-- 
cgit v1.2.3-70-g09d2


From d756ffd3daa24270361648454062e98f190ae964 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 6 Aug 2021 10:34:57 -0400
Subject: fixed webboxes so that you can link to an annotation and it will
 restore the url as well.   a bunch of warnings cleanup.

---
 src/client/util/DocumentManager.ts                        |  6 ++----
 src/client/views/DocumentButtonBar.tsx                    |  2 +-
 src/client/views/InkStrokeProperties.ts                   |  2 +-
 src/client/views/collections/CollectionSubView.tsx        |  2 +-
 src/client/views/collections/CollectionView.tsx           |  2 +-
 src/client/views/collections/TabDocView.tsx               |  4 ++--
 src/client/views/linking/LinkPopup.tsx                    |  1 -
 src/client/views/nodes/DocumentLinksButton.tsx            |  4 ++--
 src/client/views/nodes/PDFBox.tsx                         |  2 +-
 src/client/views/nodes/WebBox.tsx                         | 15 ++++++++++-----
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 src/fields/Doc.ts                                         |  2 +-
 12 files changed, 22 insertions(+), 21 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 27ae0447a..cb0ee411c 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -163,7 +163,8 @@ export class DocumentManager {
         const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
-        var targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
+        const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
+            (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
         const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
         if (!docView && annoContainerView) {
             annoContainerView.focus(targetDoc);  // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
@@ -181,9 +182,6 @@ export class DocumentManager {
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
             } else {  // otherwise try to get a view of the context of the target
-                if (annoContainerView && wasHidden) { // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
-                    targetDocContextView = annoContainerView;
-                }
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
                     targetDocContext._viewTransition = "transform 500ms";
                     targetDocContextView.props.focus(targetDocContextView.rootDoc, {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index df1e6899d..5f09a322c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -355,7 +355,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
             </div>
-            {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink != doc ? <div className="documentButtonBar-button">
+            {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">
                 <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
             </div> : (null)}
             {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 76ca5b5ec..6444e4451 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -283,7 +283,7 @@ export class InkStrokeProperties {
      */
     @action
     rotatePoint = (target: PointData, origin: PointData, angle: number) => {
-        let rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y };
+        const rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y };
         const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y;
         const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y;
         rotatedTarget.X = newX + origin.X;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a9b5ce465..227635c9b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -93,7 +93,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         childDocFilters = () => [...this.props.docFilters(), ...this.collectionFilters()];
         childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()];
         IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
-            this.props.docFilters().length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
+            this.props.docFilters().length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
         searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2ae06d2f4..e65ebf075 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -236,7 +236,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     * Shows the filter icon if it's a user-created collection which isn't a dashboard and has some docFilters applied on it or on the current dashboard.
     */
     @computed get showFilterIcon() {
-        return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered()
+        return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered();
     }
 
     @observable _subView: any = undefined;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 1f73793b6..1969d728c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -172,8 +172,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const stack: HTMLDivElement = tab.contentItem.parent;
             const header: HTMLDivElement = tab;
             stack.onscroll = action((e: any) => {
-                console.log('scrolling...')
-            })
+                console.log('scrolling...');
+            });
             const moreInfoDrag = document.createElement("div");
             moreInfoDrag.className = "lm_iconWrap";
             tab._disposers.buttonDisposer = reaction(() => this.view, view =>
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 2c4b718f4..df469c53b 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -54,7 +54,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
     @action
     onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.linkURL = e.target.value;
-        console.log(this.linkURL)
     }
 
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index b63174e54..7648e866e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -114,7 +114,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     DocumentLinksButton.StartLink = this.props.View.props.Document;
                     DocumentLinksButton.StartLinkView = this.props.View;
                 }
-            };
+            }
         }));
     }
 
@@ -291,7 +291,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                         (null)
                 }
             </div>
-        )
+        );
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b1f2070f8..23236cf20 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -217,7 +217,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
     sidebarWidth = () => !this.SidebarShown ? 0 :
         this._previewWidth ? PDFBox.openSidebarWidth :
-            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 751d63711..ca281d68f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -164,7 +164,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
 
     scrollFocus = (doc: Doc, smooth: boolean) => {
-        if (StrCast(doc.data) !== this._url) this.submitURL(StrCast(doc.data));
+        if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl));
         if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(!smooth);
         }
@@ -191,7 +191,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 annotationOn: this.rootDoc,
                 y: NumCast(this.layoutDoc._scrollTop)
             });
-        this.addDocument(anchor);
+        this.addDocumentWrapper(anchor);
         return anchor;
     }
 
@@ -444,9 +444,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return view;
     }
 
+    addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+        (doc instanceof Doc ? [doc] : doc).forEach(doc => doc.webUrl = this._url);
+        return this.addDocument(doc, annotationKey);
+    }
+
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
-        return this.addDocument(doc, sidebarKey);
+        return this.addDocumentWrapper(doc, sidebarKey);
     }
     sidebarBtnDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e, down, delta) => {
@@ -482,7 +487,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     sidebarWidth = () => !this.SidebarShown ? 0 :
         this._previewWidth ? WebBox.openSidebarWidth :
             (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
-            NumCast(this.layoutDoc.nativeWidth);
+            NumCast(this.layoutDoc.nativeWidth)
 
     @computed get content() {
         return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
@@ -561,7 +566,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             anchorMenuClick={this.anchorMenuClick}
                             scrollTop={0}
                             down={this._marqueeing} scaling={returnOne}
-                            addDocument={this.addDocument}
+                            addDocument={this.addDocumentWrapper}
                             docView={this.props.docViewPath().lastElement()}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this._savedAnnotations}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e5c36ea71..edd7177de 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -215,7 +215,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         AnchorMenu.Instance.Status = "marquee";
         AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
             this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
-            console.log("highlight")
             return undefined;
         });
         /**
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6dcf34a3a..0cbfaf067 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1214,7 +1214,7 @@ export namespace Doc {
             case DocumentType.RTF: return "sticky-note";
             case DocumentType.COL:
                 const folder: IconProp = isOpen ? "folder-open" : "folder";
-                const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right"
+                const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
                 return !doc?.isFolder ? folder : chevron;
             case DocumentType.WEB: return "globe-asia";
             case DocumentType.SCREENSHOT: return "photo-video";
-- 
cgit v1.2.3-70-g09d2


From e7bbdd3b489fea1c508af53345cd0d1f31685cb9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 6 Aug 2021 18:03:38 -0400
Subject: collabortion fixes: added new acl for allowing people to edit their
 own text within the same note, fixed playground fields to write to the server
 without updating other clients.

---
 src/client/DocServer.ts                            |  5 +-
 src/client/documents/Documents.ts                  | 10 ++--
 src/client/util/CurrentUserUtils.ts                |  6 +-
 src/client/util/SharingManager.tsx                 | 13 ++--
 src/client/views/DocComponent.tsx                  |  6 +-
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/MarqueeAnnotator.tsx              |  4 +-
 src/client/views/PropertiesView.tsx                | 14 ++---
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 70 ++++++++++++++--------
 src/fields/Doc.ts                                  |  6 +-
 src/fields/util.ts                                 | 22 ++++---
 13 files changed, 105 insertions(+), 69 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 59278d2af..d9ae7d64c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -59,7 +59,10 @@ export namespace DocServer {
     export var PlaygroundFields: string[];
     export function setPlaygroundFields(livePlaygroundFields: string[]) {
         DocServer.PlaygroundFields = livePlaygroundFields;
-        livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
+        livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+    }
+    export function IsPlaygroundField(field: string) {
+        return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ""));
     }
 
     export function setFieldWriteMode(field: string, writeMode: WriteMode) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 24f777e88..93f0880a4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -577,7 +577,7 @@ export namespace Docs {
             dataProps.creationDate = new DateField;
             dataProps[`${fieldKey}-lastModified`] = new DateField;
             dataProps["acl-Override"] = "None";
-            dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+            dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
 
             dataProps[fieldKey] = data;
 
@@ -588,7 +588,7 @@ export namespace Docs {
 
             viewProps.author = Doc.CurrentUserEmail;
             viewProps["acl-Override"] = "None";
-            viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+            viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
             ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
 
@@ -699,7 +699,7 @@ export namespace Docs {
             I.author = Doc.CurrentUserEmail;
             I.rotation = 0;
             I.data = new InkField(points);
-            I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+            I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             I["acl-Override"] = "None";
             I[Initializing] = false;
             return I;
@@ -1038,8 +1038,8 @@ export namespace DocUtils {
             title: ComputedField.MakeFunction("generateLinkTitle(self)") as any,
             "anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined,
             "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
-            "acl-Public": SharingPermissions.Add,
-            "_acl-Public": SharingPermissions.Add,
+            "acl-Public": SharingPermissions.Augment,
+            "_acl-Public": SharingPermissions.Augment,
             layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null),
             linkDisplay: true, hidden: true,
             linkRelationship,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1af6607a7..14c43fb1c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -917,7 +917,7 @@ export class CurrentUserUtils {
                 linkDocs = new Doc(linkDatabaseId, true);
                 (linkDocs as Doc).author = Doc.CurrentUserEmail;
                 (linkDocs as Doc).data = new List<Doc>([]);
-                (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add;
+                (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
             }
             doc.myLinkDatabase = new PrefetchProxy(linkDocs);
         }
@@ -926,10 +926,10 @@ export class CurrentUserUtils {
             if (!sharedDocs) {
                 sharedDocs = Docs.Create.TreeDocument([], {
                     title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
-                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add,
+                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
                     _chromeHidden: true, boxShadow: "0 0",
                 }, sharingDocumentId + "outer", sharingDocumentId);
-                (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add;
+                (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
             }
             if (sharedDocs instanceof Doc) {
                 Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index d283510b7..a8972b988 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import * as React from "react";
 import Select from "react-select";
 import * as RequestPromise from "request-promise";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
@@ -85,7 +85,8 @@ export class SharingManager extends React.Component<{}> {
     private AclMap = new Map<symbol, string>([
         [AclPrivate, SharingPermissions.None],
         [AclReadonly, SharingPermissions.View],
-        [AclAddonly, SharingPermissions.Add],
+        [AclAugment, SharingPermissions.Augment],
+        [AclSelfEdit, SharingPermissions.SelfEdit],
         [AclEdit, SharingPermissions.Edit],
         [AclAdmin, SharingPermissions.Admin]
     ]);
@@ -101,7 +102,7 @@ export class SharingManager extends React.Component<{}> {
             this.targetDoc = target_doc || target?.props.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = this.targetDoc !== undefined;
-            this.permissions = SharingPermissions.Add;
+            this.permissions = SharingPermissions.Augment;
         });
     }
 
@@ -366,10 +367,10 @@ export class SharingManager extends React.Component<{}> {
         const dropdownValues: string[] = Object.values(SharingPermissions);
         if (!uniform) dropdownValues.unshift("-multiple-");
         if (override) dropdownValues.unshift("None");
-        return dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission =>
+        return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
             (
                 <option key={permission} value={permission}>
-                    {permission === SharingPermissions.Add ? "Can Augment" : permission}
+                    {permission}
                 </option>
             )
         );
@@ -546,7 +547,7 @@ export class SharingManager extends React.Component<{}> {
                             </select>
                         ) : (
                                 <div className={"permissions-dropdown"}>
-                                    {permissions === SharingPermissions.Add ? "Can Augment" : permissions}
+                                    {permissions}
                                 </div>
                             )}
                     </div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index fc36c7e43..99c695a4a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast, AclAdmin } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAugment, AclPrivate, AclEdit, AclSym, DocListCast, AclAdmin, AclSelfEdit } from '../../fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -131,7 +131,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
             const indocs = doc instanceof Doc ? [doc] : doc;
-            const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+            const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
             if (docs.length) {
                 setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
                     Doc.SetInPlace(doc, "isPushpin", undefined, true);
@@ -199,7 +199,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
                         });
                     }
 
-                    if (effectiveAcl === AclAddonly) {
+                    if (effectiveAcl === AclAugment) {
                         added.map(doc => {
                             if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
                             doc.context = this.props.Document;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 005e46836..8f37172a0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -107,7 +107,7 @@ export class MainView extends React.Component {
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
         if (!MainView.Live) {
-            DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "showSidebar", "_sidebarWidthPercent", "_width", "_height", "width", "height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeHidden", "nativeWidth", "_nativeWidth"]); // can play with these fields on someone else's
+            DocServer.setPlaygroundFields(["dataTransition", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition", "panX", "panY", "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
         }
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
 
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 805cda95c..a3a3bce56 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,6 +1,6 @@
 import { action, observable, ObservableMap, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
 import { List } from "../../fields/List";
 import { NumCast } from "../../fields/Types";
@@ -156,7 +156,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
         // creates annotation documents for current highlights
         const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
-        const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
+        const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
         !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
         return annotationDoc as Doc ?? undefined;
     }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 8136edf04..de437e1df 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,7 +5,7 @@ import { intersection } from "lodash";
 import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { ColorState, SketchPicker } from "react-color";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym, AclSelfEdit } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
 import { InkField } from "../../fields/InkField";
 import { ComputedField } from "../../fields/ScriptField";
@@ -342,12 +342,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return <select className="permissions-select"
             value={permission}
             onChange={e => this.changePermissions(e, user)}>
-            {dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission => {
-                return (
-                    <option key={permission} value={permission}>
-                        {permission === SharingPermissions.Add ? "Can Augment" : permission}
-                    </option>);
-            })}
+            {dropdownValues.filter(permission =>
+                !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
+                    <option key={permission} value={permission}>  {permission}  </option>)}
         </select>;
     }
 
@@ -402,7 +399,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             [AclUnset, "None"],
             [AclPrivate, SharingPermissions.None],
             [AclReadonly, SharingPermissions.View],
-            [AclAddonly, SharingPermissions.Add],
+            [AclAugment, SharingPermissions.Augment],
+            [AclSelfEdit, SharingPermissions.SelfEdit],
             [AclEdit, SharingPermissions.Edit],
             [AclAdmin, SharingPermissions.Admin]
         ]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 846d28214..19da7ea00 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
@@ -298,7 +298,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this._downX = x;
             this._downY = y;
             const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
-            if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) {
+            if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
                 PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
             }
             this.clearSelection();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index edd7177de..f7bed2fa1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { ReplaceStep } from 'prosemirror-transform';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
@@ -253,7 +253,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
                 json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
 
-            if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
+            if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) {
                 const accumTags = [] as string[];
                 state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
                     if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
@@ -1401,6 +1401,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._rules!.EnteringStyle = false;
         }
         e.stopPropagation();
+        for (var i = state.selection.from; i < state.selection.to; i++) {
+            const node = state.doc.resolve(i);
+            if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+                mark.attrs.userid !== Doc.CurrentUserEmail) &&
+                [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+                e.preventDefault();
+            }
+        }
         switch (e.key) {
             case "Escape":
                 this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index d5c77786c..1f78b2204 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,13 +7,14 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast, AclAugment } from "../../../../fields/Doc";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { Id } from "../../../../fields/FieldSymbols";
 import { Docs } from "../../../documents/Documents";
 import { Utils } from "../../../../Utils";
 import { listSpec } from "../../../../fields/Schema";
 import { List } from "../../../../fields/List";
+import { GetEffectiveAcl } from "../../../../fields/util";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
 
@@ -70,25 +71,39 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         return false;
     };
 
+    const canEdit = (state: any) => {
+        for (var i = state.selection.from; i < state.selection.to; i++) {
+            const node = state.doc.resolve(i);
+            if (node?.marks?.().some((mark: any) => mark.type === schema.marks.user_mark &&
+                mark.attrs.userid !== Doc.CurrentUserEmail) &&
+                GetEffectiveAcl(props.Document) === AclAugment) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+
     //History commands
     bind("Mod-z", undo);
     bind("Shift-Mod-z", redo);
     !mac && bind("Mod-y", redo);
 
     //Commands to modify Mark
-    bind("Mod-b", toggleMark(schema.marks.strong));
-    bind("Mod-B", toggleMark(schema.marks.strong));
+    bind("Mod-b", toggleEditableMark(schema.marks.strong));
+    bind("Mod-B", toggleEditableMark(schema.marks.strong));
 
-    bind("Mod-e", toggleMark(schema.marks.em));
-    bind("Mod-E", toggleMark(schema.marks.em));
+    bind("Mod-e", toggleEditableMark(schema.marks.em));
+    bind("Mod-E", toggleEditableMark(schema.marks.em));
 
-    bind("Mod-*", toggleMark(schema.marks.code));
+    bind("Mod-*", toggleEditableMark(schema.marks.code));
 
-    bind("Mod-u", toggleMark(schema.marks.underline));
-    bind("Mod-U", toggleMark(schema.marks.underline));
+    bind("Mod-u", toggleEditableMark(schema.marks.underline));
+    bind("Mod-U", toggleEditableMark(schema.marks.underline));
 
     //Commands for lists
-    bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
+    bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any));
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         /// bcz; Argh!!  replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
@@ -96,6 +111,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             if (!props.LayoutTemplateString) return addTextBox(false, true);
             return true;
         }
+        if (!canEdit(state)) return true;
         const ref = state.selection;
         const range = ref.$from.blockRange(ref.$to);
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -121,6 +137,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         /// bcz; Argh!!  replace with a onShiftTab prop conditionally handles Tab);
         if (props.Document._singleLine) return true;
+        if (!canEdit(state)) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
 
         if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
@@ -140,24 +157,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
 
     //Commands to modify BlockType
-    bind("Ctrl->", wrapIn(schema.nodes.blockquote));
-    bind("Alt-\\", setBlockType(schema.nodes.paragraph));
-    bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+    bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any)));
+    bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any));
+    bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any));
 
-    bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })));
-    });
+    bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
 
     for (let i = 1; i <= 6; i++) {
-        bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+        bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch as any));
     }
 
     //Command to create a horizontal break line
     const hr = schema.nodes.horizontal_rule;
-    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
-        return true;
-    });
+    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
 
     //Command to unselect all
     bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -173,13 +185,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     };
 
     //Command to create a text document to the right of the selected textbox
-    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => addTextBox(false, true));
+    bind("Alt-Enter", () => addTextBox(false, true));
 
     //Command to create a text document to the bottom of the selected textbox
-    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => addTextBox(true, true));
+    bind("Ctrl-Enter", () => addTextBox(true, true));
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
     bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+        if (!canEdit(state)) return true;
+
         if (!deleteSelection(state, (tx: Transaction<Schema<any, any>>) => {
             dispatch(updateBullets(tx, schema));
         })) {
@@ -200,6 +214,9 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //command to break line
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
         if (addTextBox(true, false)) return true;
+
+        if (!canEdit(state)) return true;
+
         const trange = state.selection.$from.blockRange(state.selection.$to);
         const path = (state.selection.$from as any).path;
         const depth = trange ? liftTarget(trange) : undefined;
@@ -238,18 +255,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     //Command to create a blank space
     bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        if (!canEdit(state)) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         dispatch(splitMetadata(marks, state.tr));
         return false;
     });
 
-    bind("Alt-ArrowUp", joinUp);
-    bind("Alt-ArrowDown", joinDown);
-    bind("Mod-BracketLeft", lift);
+    bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
+    bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
+    bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
 
     const cmd = chainCommands(exitCode, (state, dispatch) => {
         if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+            canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
             return true;
         }
         return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0cbfaf067..85ea3cfa9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -90,7 +90,8 @@ export const DirectLinksSym = Symbol("DirectLinks");
 export const AclUnset = Symbol("AclUnset");
 export const AclPrivate = Symbol("AclOwnerOnly");
 export const AclReadonly = Symbol("AclReadOnly");
-export const AclAddonly = Symbol("AclAddonly");
+export const AclAugment = Symbol("AclAugment");
+export const AclSelfEdit = Symbol("AclSelfEdit");
 export const AclEdit = Symbol("AclEdit");
 export const AclAdmin = Symbol("AclAdmin");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
@@ -102,7 +103,8 @@ const AclMap = new Map<string, symbol>([
     ["None", AclUnset],
     [SharingPermissions.None, AclPrivate],
     [SharingPermissions.View, AclReadonly],
-    [SharingPermissions.Add, AclAddonly],
+    [SharingPermissions.Augment, AclAugment],
+    [SharingPermissions.SelfEdit, AclSelfEdit],
     [SharingPermissions.Edit, AclEdit],
     [SharingPermissions.Admin, AclAdmin]
 ]);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 526e5af72..2bb6b45c2 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -14,6 +14,7 @@ import CursorField from "./CursorField";
 import { List } from "./List";
 import { SnappingManager } from "../client/util/SnappingManager";
 import { computedFn } from "mobx-utils";
+import { RichTextField } from "./RichTextField";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -77,7 +78,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     const fromServer = target[UpdatingFromServer];
     const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
     const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
-    const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !DocServer.Control.isReadOnly();// && !playgroundMode;
+    const writeToServer =
+        (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
+        !DocServer.Control.isReadOnly();
 
     if (writeToDoc) {
         if (value === undefined) {
@@ -157,9 +160,10 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
  */
 export enum SharingPermissions {
     Admin = "Admin",
-    Edit = "Can Edit",
-    Add = "Can Augment",
-    View = "Can View",
+    Edit = "Edit",
+    SelfEdit = "Self Edit",
+    Augment = "Augment",
+    View = "View",
     None = "Not Shared"
 }
 
@@ -176,7 +180,7 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
 
 function getPropAcl(target: any, prop: string | symbol | number) {
     if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin;  // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
-    if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable
+    if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
     return GetEffectiveAcl(target);
 }
 
@@ -192,7 +196,8 @@ function getEffectiveAcl(target: any, user?: string): symbol {
         HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
             [AclPrivate, 0],
             [AclReadonly, 1],
-            [AclAddonly, 2],
+            [AclAugment, 2],
+            [AclSelfEdit, 2.5],
             [AclEdit, 3],
             [AclAdmin, 4]
         ]);
@@ -235,6 +240,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         ["Not Shared", 0],
         ["Can View", 1],
         ["Can Augment", 2],
+        ["Self Edit", 2.5],
         ["Can Edit", 3],
         ["Admin", 4]
     ]);
@@ -294,7 +300,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
     const effectiveAcl = getPropAcl(target, prop);
-    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
+    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true;
     // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
-- 
cgit v1.2.3-70-g09d2


From 81ae5175aebf7e14a781a73d4a7cbaec0b8f3e4a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 9 Aug 2021 14:26:14 -0400
Subject: fixed augment only text to resize correctly with autoheight. fixed
 acls to cache correctly for new documents. fixed links to be shared across
 users.  fixed links to always default to public.

---
 src/client/DocServer.ts                            |  2 +-
 src/client/documents/Documents.ts                  |  8 ++-
 src/client/util/CurrentUserUtils.ts                | 16 +++---
 src/client/util/LinkManager.ts                     | 67 ++++++++++++++--------
 src/client/util/SettingsManager.tsx                |  3 +-
 src/client/util/SharingManager.tsx                 |  4 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++---
 src/fields/util.ts                                 |  4 +-
 9 files changed, 79 insertions(+), 47 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 5712a1d64..f4e3a22cf 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
 import * as io from 'socket.io-client';
 import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, UpdatingFromServer } from '../fields/Doc';
+import { Opt, Doc, UpdatingFromServer, updateCachedAcls } from '../fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../fields/RefField';
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 93f0880a4..48886aa3b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
 import { action, runInAction } from "mobx";
 import { basename, extname } from "path";
 import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing, updateCachedAcls } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
 import { HtmlField } from "../../fields/HtmlField";
 import { InkField } from "../../fields/InkField";
@@ -577,7 +577,7 @@ export namespace Docs {
             dataProps.creationDate = new DateField;
             dataProps[`${fieldKey}-lastModified`] = new DateField;
             dataProps["acl-Override"] = "None";
-            dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+            dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
 
             dataProps[fieldKey] = data;
 
@@ -588,13 +588,15 @@ export namespace Docs {
 
             viewProps.author = Doc.CurrentUserEmail;
             viewProps["acl-Override"] = "None";
-            viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+            viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
             ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
 
             !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
                 !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc);
 
+            updateCachedAcls(dataDoc);
+            updateCachedAcls(viewDoc);
             return viewDoc;
         }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 14c43fb1c..34990e121 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,13 +2,14 @@ import { computed, observable, reaction } from "mobx";
 import * as rp from 'request-promise';
 import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
+import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
 import { listSpec } from "../../fields/Schema";
 import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
 import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
 import { nullAudio } from "../../fields/URLField";
 import { SharingPermissions } from "../../fields/util";
 import { Utils } from "../../Utils";
@@ -19,6 +20,7 @@ import { Networking } from "../Network";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
 import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { LabelBox } from "../views/nodes/LabelBox";
@@ -31,13 +33,10 @@ import { LinkManager } from "./LinkManager";
 import { Scripting } from "./Scripting";
 import { SearchUtil } from "./SearchUtil";
 import { SelectionManager } from "./SelectionManager";
-import { UndoManager } from "./UndoManager";
-import { SnappingManager } from "./SnappingManager";
-import { InkTool } from "../../fields/InkField";
-import { SharingManager } from "./SharingManager";
-import { computedFn } from "mobx-utils";
 import { ColorScheme } from "./SettingsManager";
-import { Colors } from "../views/global/globalEnums";
+import { SharingManager } from "./SharingManager";
+import { SnappingManager } from "./SnappingManager";
+import { UndoManager } from "./UndoManager";
 
 
 export let resolvedPorts: { server: number, socket: number };
@@ -915,6 +914,7 @@ export class CurrentUserUtils {
             let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
             if (!linkDocs) {
                 linkDocs = new Doc(linkDatabaseId, true);
+                (linkDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
                 (linkDocs as Doc).author = Doc.CurrentUserEmail;
                 (linkDocs as Doc).data = new List<Doc>([]);
                 (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
@@ -1029,7 +1029,7 @@ export class CurrentUserUtils {
         doc.fontFamily = StrCast(doc.fontFamily, "Arial");
         doc.fontColor = StrCast(doc.fontColor, "black");
         doc.fontHighlight = StrCast(doc.fontHighlight, "");
-        doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, true);
+        doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
         doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white");
         doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
         doc.noviceMode = BoolCast(doc.noviceMode, true);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8abfd740c..933b98a8c 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { observable, observe, action } from "mobx";
+import { observable, observe, action, reaction, computed } from "mobx";
 import { computedFn } from "mobx-utils";
 import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
 import { List } from "../../fields/List";
@@ -26,36 +26,41 @@ type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => vo
 export class LinkManager {
 
     @observable static _instance: LinkManager;
-    @observable static userDocs: Doc[] = [];
+    @observable static userLinkDBs: Doc[] = [];
     public static currentLink: Opt<Doc>;
     public static get Instance() { return LinkManager._instance; }
+    public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb);
+    static links: Doc[] = [];
     constructor() {
         LinkManager._instance = this;
         setTimeout(() => {
-            LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
-            const addLinkToDoc = action((link: Doc): any => {
+            LinkManager.userLinkDBs = [];
+            const addLinkToDoc = (link: Doc) => {
                 const a1 = link?.anchor1;
                 const a2 = link?.anchor2;
-                if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
-                if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
-                    Doc.GetProto(a1)[DirectLinksSym].add(link);
-                    Doc.GetProto(a2)[DirectLinksSym].add(link);
-                    Doc.GetProto(link)[DirectLinksSym].add(link);
-                }
-            });
-            const remLinkFromDoc = action((link: Doc): any => {
+                Promise.all([a1, a2]).then(action(() => {
+                    if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+                        Doc.GetProto(a1)[DirectLinksSym].add(link);
+                        Doc.GetProto(a2)[DirectLinksSym].add(link);
+                        Doc.GetProto(link)[DirectLinksSym].add(link);
+                    }
+                }));
+            }
+            const remLinkFromDoc = (link: Doc) => {
                 const a1 = link?.anchor1;
                 const a2 = link?.anchor2;
-                if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
-                if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
-                    Doc.GetProto(a1)[DirectLinksSym].delete(link);
-                    Doc.GetProto(a2)[DirectLinksSym].delete(link);
-                    Doc.GetProto(link)[DirectLinksSym].delete(link);
-                }
-            });
-            const watchUserLinks = (userLinks: List<Doc>) => {
+                Promise.all([a1, a2]).then(action(() => {
+                    if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+                        Doc.GetProto(a1)[DirectLinksSym].delete(link);
+                        Doc.GetProto(a2)[DirectLinksSym].delete(link);
+                        Doc.GetProto(link)[DirectLinksSym].delete(link);
+                    }
+                }));
+            }
+            const watchUserLinkDB = (userLinkDBDoc: Doc) => {
+                LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
                 const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field;  // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
-                observe(userLinks, change => {
+                observe(userLinkDBDoc.data as Doc, change => {  // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
                     switch (change.type as any) {
                         case "splice":
                             (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
@@ -64,13 +69,29 @@ export class LinkManager {
                         case "update": //let oldValue = change.oldValue;
                     }
                 }, true);
+                observe(userLinkDBDoc, "data",  // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
+                    change => {
+                        switch (change.type as any) {
+                            case "update":
+                                Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => {
+                                    const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length);
+                                    const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length);
+
+                                    const added = newDocs?.filter(link => !(oldDocs || []).includes(link));
+                                    const removed = oldDocs?.filter(link => !(newDocs || []).includes(link));
+                                    added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
+                                    removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+                                });
+                        }
+                    }, true);
             };
-            observe(LinkManager.userDocs, change => {
+            observe(LinkManager.userLinkDBs, change => {
                 switch (change.type as any) {
-                    case "splice": (change as any).added.forEach(watchUserLinks); break;
+                    case "splice": (change as any).added.forEach(watchUserLinkDB); break;
                     case "update": //let oldValue = change.oldValue;
                 }
             }, true);
+            LinkManager.addLinkDB(Doc.LinkDBDoc());
         });
     }
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 3987497b8..bd91db779 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -268,7 +268,8 @@ export class SettingsManager extends React.Component<{}> {
                 <div className="tab-column-content">
                     <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
                     <div className="default-acl">
-                        <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)} onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
+                        <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)}
+                            onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
                         <div className="acl-text">Default access private</div>
                     </div>
                 </div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index a8972b988..6c4556250 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -23,6 +23,7 @@ import { GroupManager, UserOptions } from "./GroupManager";
 import { GroupMemberView } from "./GroupMemberView";
 import { SelectionManager } from "./SelectionManager";
 import "./SharingManager.scss";
+import { LinkManager } from "./LinkManager";
 
 export interface User {
     email: string;
@@ -154,10 +155,11 @@ export class SharingManager extends React.Component<{}> {
                 }
             });
             return Promise.all(evaluating).then(() => {
-                runInAction(() => {
+                runInAction(async () => {
                     for (const sharer of sharingDocs) {
                         if (!this.users.find(user => user.user.email === sharer.user.email)) {
                             this.users.push(sharer);
+                            LinkManager.addLinkDB(sharer.linkDatabase);
                         }
                     }
                 });
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2be3c3135..3d7446cbe 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -108,7 +108,7 @@ export class MainView extends React.Component {
         this._sidebarContent.proto = undefined;
         if (!MainView.Live) {
             DocServer.setPlaygroundFields(["dataTransition", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
-                "panX", "panY", "width", "height", "nativeWidth", "nativeHeight",
+                "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height",
                 "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
         }
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f7bed2fa1..9bba15b28 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -431,6 +431,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.ProseRef = ele;
         this._dropDisposer?.();
         ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+        // if (this.autoHeight) this.tryUpdateScrollHeight(); 
     }
 
     @undoBatch
@@ -1438,16 +1439,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         }
     }
-    tryUpdateScrollHeight() {
+    tryUpdateScrollHeight = () => {
         if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
             const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-            const proseHeight = !this.ProseRef ? 0 : Array.from(this.ProseRef.children[0].children).reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-            if (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) {
-                    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...
+            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 scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                if (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) {
+                        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...
+                }
             }
         }
     }
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 2bb6b45c2..439c4d333 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -141,7 +141,9 @@ export function denormalizeEmail(email: string) {
 export function inheritParentAcls(parent: Doc, child: Doc) {
     const dataDoc = parent[DataSym];
     for (const key of Object.keys(dataDoc)) {
-        key.startsWith("acl") && distributeAcls(key, dataDoc[key], child);
+        // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
+        const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key];
+        key.startsWith("acl") && distributeAcls(key, permission, child);
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 136b900ed0b939c2a10b601470f764dcb50809ad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 12 Aug 2021 17:07:41 -0400
Subject: fixed textboxes which were generally a line too short when opened in
 contentfitting view (e.g. stackingView or pivotView)

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9bba15b28..1058070f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1444,12 +1444,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             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 scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+                var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
                 if (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) {
                         setScrollHeight();
+                        setTimeout(() => {
+                            proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+                            scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                            setScrollHeight();
+                        }, 10);
                     } 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 bd41980b6d8849c5ae875fbe5325f88373b3640b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Aug 2021 11:58:21 -0400
Subject: fixed canEdit func in prosemirrorExampleTransfer to prevent error
 when hitting enter at end of uneditable doc.   fixed scrollheight setting
 error in formattedtextbox caused by assigning null

---
 .../views/nodes/formattedText/FormattedTextBox.tsx    |  6 +++---
 .../nodes/formattedText/ProsemirrorExampleTransfer.ts | 19 +++++++++++--------
 2 files changed, 14 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1058070f8..b0b8ce75d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -535,7 +535,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
             const min = Math.round(Date.now() / 1000 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
-            setTimeout(() => this.updateHighlights());
+            setTimeout(this.updateHighlights);
         }
         if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
@@ -882,7 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     }
                 },
             );
-            if (this._recording) setTimeout(() => this.recordDictation());
+            if (this._recording) setTimeout(this.recordDictation);
         }
         var quickScroll: string | undefined = "";
         this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
@@ -1453,7 +1453,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         setTimeout(() => {
                             proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
                             scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-                            setScrollHeight();
+                            scrollHeight && setScrollHeight();
                         }, 10);
                     } 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...
                 }
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 1f78b2204..eff400a98 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast, AclAugment } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { Id } from "../../../../fields/FieldSymbols";
 import { Docs } from "../../../documents/Documents";
@@ -72,13 +72,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     };
 
     const canEdit = (state: any) => {
-        for (var i = state.selection.from; i < state.selection.to; i++) {
-            const node = state.doc.resolve(i);
-            if (node?.marks?.().some((mark: any) => mark.type === schema.marks.user_mark &&
-                mark.attrs.userid !== Doc.CurrentUserEmail) &&
-                GetEffectiveAcl(props.Document) === AclAugment) {
-                return false;
-            }
+        switch (GetEffectiveAcl(props.Document)) {
+            case AclAugment: return false;
+            case AclSelfEdit:
+                for (var i = state.selection.from; i < state.selection.to; i++) {
+                    const marks = state.doc.resolve(i)?.marks?.();
+                    if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
+                        return false;
+                    }
+                }
+                break;
         }
         return true;
     }
-- 
cgit v1.2.3-70-g09d2


From cc89a0c16246afde8bb3d891a4ede492b6644b1f Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Wed, 18 Aug 2021 11:01:57 -0400
Subject: added search linking

---
 src/Utils.ts                                              |  1 +
 src/client/views/MarqueeAnnotator.tsx                     |  2 ++
 src/client/views/linking/LinkPopup.tsx                    |  5 ++++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  1 +
 src/client/views/pdf/AnchorMenu.tsx                       | 12 +++++-------
 src/client/views/search/SearchBox.tsx                     | 14 +++++++++++---
 src/client/views/topbar/TopBar.scss                       |  1 +
 7 files changed, 25 insertions(+), 11 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 194c38a6f..f9ca8646f 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -439,6 +439,7 @@ export function emptyFunction() { }
 
 export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
 
+
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
 
 export type Predicate<K, V> = (entry: [K, V]) => boolean;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a3a3bce56..fa1a4cfea 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -68,6 +68,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
         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;
+
         /**
          * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
          * It also initiates a Drag/Drop interaction to place the text annotation.
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 85fd91d9f..c8be9069c 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -11,9 +11,11 @@ import { SearchBox } from '../search/SearchBox';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './LinkPopup.scss';
 import React = require("react");
+import { Doc, Opt } from '../../../fields/Doc';
 
 interface LinkPopupProps {
     showPopup: boolean;
+    linkFrom?: () => Doc | undefined;
     // groupType: string;
     // linkDoc: Doc;
     // docView: DocumentView;
@@ -48,6 +50,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
 
     render() {
         const popupVisibility = this.props.showPopup ? "block" : "none";
+        const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined;
         return (
             <div className="linkPopup-container" style={{ display: popupVisibility }}>
                 {/* <div className="linkPopup-url-container">
@@ -67,7 +70,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                     <SearchBox 
                         Document={CurrentUserUtils.MySearchPanelDoc}
                         DataDoc={CurrentUserUtils.MySearchPanelDoc}
-                        // linkFrom={}
+                        linkFrom={linkDoc}
                         linkSearch={true}
                         fieldKey="data"
                         dropAction="move"
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b0b8ce75d..d1027dfd7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -217,6 +217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
             return undefined;
         });
+        AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
         /**
          * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
          * It also initiates a Drag/Drop interaction to place the text annotation.
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d098791e5..8d74b2ac4 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -45,8 +45,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable public Highlighting: boolean = false;
     @observable public Status: "marquee" | "annotation" | "" = "";
 
+    public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
-    public OnLink: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
     public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
@@ -67,15 +68,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(() => SelectionManager.Views(),
             selected => {
-                AnchorMenu.Instance.fadeOut(true)
                 this._showLinkPopup = false;
+                console.log("unmount");
+                AnchorMenu.Instance.fadeOut(true)
             });
     }
 
-    componentWillUnmount() {
-        console.log("unmount");
-    }
-
     pointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e: PointerEvent) => {
             this.StartDrag(e, this._commentCont.current!);
@@ -159,7 +157,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         <FontAwesomeIcon icon="link" size="lg" />
                     </button>
                 </Tooltip>,
-                <LinkPopup showPopup={this._showLinkPopup}/>
+                <LinkPopup showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor}/>
             ] : [
                 <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
                     <button className="antimodeMenu-button" onPointerDown={this.Delete}>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 67c7fc845..07186fe8d 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -23,7 +23,7 @@ const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
 
 export interface SearchBoxProps extends FieldViewProps {
     linkSearch: boolean;
-    // linkFrom: Doc;
+    linkFrom?: (() => Doc | undefined) | undefined;
 }
 
 /**
@@ -108,8 +108,16 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
         this._selectedResult = doc;
     });
 
-    makeLink = action((doc: Doc) => {
-        DocUtils.MakeLink({doc: doc}, {doc:doc});
+    makeLink = action((linkTo: Doc) => {
+        console.log("[makeLink-1] got here!")
+        console.log(linkTo.title);
+        if (this.props.linkFrom){
+            const linkFrom = this.props.linkFrom();
+            if (linkFrom){
+                console.log(linkFrom.title);
+                DocUtils.MakeLink({doc: linkFrom}, {doc:linkTo});
+            }
+        }
     });
 
     /**
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 2ecbb536b..d04ae8a80 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -53,6 +53,7 @@
             .topbar-dashboards {
                 display: flex;
                 flex-direction: row;
+                gap: 5px;
             }
 
             .topbar-lozenge-dashboard {
-- 
cgit v1.2.3-70-g09d2


From 2d8b3c6b73da1b7685903697525a277fd53340a5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 26 Aug 2021 00:32:47 -0400
Subject: a lot of changes to move isContentActive into DocumentView

---
 src/Utils.ts                                       |  2 +-
 src/client/views/DocComponent.tsx                  | 23 ++++++++----------
 src/client/views/collections/CollectionSubView.tsx |  3 +--
 src/client/views/collections/CollectionView.tsx    |  8 ++++++-
 src/client/views/collections/TabDocView.tsx        |  3 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 ++++----
 src/client/views/nodes/AudioBox.tsx                |  8 +++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 +++--
 src/client/views/nodes/ComparisonBox.tsx           | 17 ++++++++++----
 src/client/views/nodes/DocumentView.tsx            | 15 ++++++++----
 src/client/views/nodes/ImageBox.tsx                |  3 +--
 src/client/views/nodes/PDFBox.tsx                  | 11 ++++-----
 src/client/views/nodes/VideoBox.tsx                | 27 +++++++++++-----------
 src/client/views/nodes/WebBox.tsx                  | 12 ++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++----
 src/client/views/pdf/AnchorMenu.tsx                |  1 -
 src/client/views/pdf/PDFViewer.tsx                 |  3 +--
 src/fields/Doc.ts                                  |  3 +++
 18 files changed, 91 insertions(+), 74 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 102ac520b..de3b13f63 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -116,7 +116,7 @@ export namespace Utils {
     }
 
     const isTransparentFunctionHack = "isTransparent(__value__)";
-    const noRecursionHack = "__noRecursion";
+    export const noRecursionHack = "__noRecursion";
     export function IsRecursiveFilter(val: string) {
         return !val.includes(noRecursionHack);
     }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d9cc29bed..33dff9da5 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,17 +1,17 @@
-import { Doc, Opt, DataSym, AclReadonly, AclAugment, AclPrivate, AclEdit, AclSym, DocListCast, AclAdmin, AclSelfEdit } from '../../fields/Doc';
-import { Touchable } from './Touchable';
-import { computed, action, observable } from 'mobx';
-import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
+import { action, computed, observable } from 'mobx';
+import { DateField } from '../../fields/DateField';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, Opt } from '../../fields/Doc';
 import { InkTool } from '../../fields/InkField';
-import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
-import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail, inheritParentAcls } from '../../fields/util';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { DocUtils } from '../documents/Documents';
+import { Cast, ScriptCast } from '../../fields/Types';
+import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
 import { returnFalse } from '../../Utils';
+import { DocUtils } from '../documents/Documents';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { InteractionUtils } from '../util/InteractionUtils';
 import { UndoManager } from '../util/UndoManager';
+import { Touchable } from './Touchable';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -107,6 +107,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
+        isAnyChildContentActive = () => this._isAnyChildContentActive;
 
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
@@ -229,10 +230,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         }
 
         whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
-        isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None ||
-            (this.props.isContentActive?.() || this.props.Document.forceActive ||
-                this.props.isSelected(outsideReaction) || this._isAnyChildContentActive ||
-                this.props.rootSelected(outsideReaction)) ? true : false)
     }
     return Component;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 1f4c35daa..b70df93da 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -23,6 +23,7 @@ import ReactLoading from 'react-loading';
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     SetSubView?: (subView: any) => void;
+    isAnyChildContentActive: () => boolean;
 }
 
 export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: X) {
@@ -490,7 +491,5 @@ import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTex
 import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { OverlayView } from "../OverlayView";
-import { Hypothesis } from "../../util/HypothesisUtils";
 import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
-import { FilterBox } from "../nodes/FilterBox";
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a821aeeea..229e93b9e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -37,6 +37,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import './CollectionView.scss';
 import { returnEmptyString } from '../../../Utils';
+import { InkTool } from '../../../fields/InkField';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 
@@ -119,7 +120,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
         // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
         // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
-        this.isContentActive();
+        //this.isContentActive();
     }
 
     screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
@@ -253,6 +254,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
 
     @observable _subView: any = undefined;
 
+    isContentActive = (outsideReaction?: boolean) => {
+        return this.props.isContentActive() ? true : false;
+    }
     render() {
         TraceMobx();
         const props: SubCollectionViewProps = {
@@ -262,6 +266,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             moveDocument: this.moveDocument,
             removeDocument: this.removeDocument,
             isContentActive: this.isContentActive,
+            isAnyChildContentActive: this.isAnyChildContentActive,
+            whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
             PanelWidth: this.bodyPanelWidth,
             PanelHeight: this.props.PanelHeight,
             ScreenToLocalTransform: this.screenToLocalTransform,
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e24fcf372..117dba4de 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -480,7 +480,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
                     setHeight={returnFalse}
-                    isContentActive={returnTrue}
+                    isContentActive={returnFalse}
+                    isAnyChildContentActive={returnFalse}
                     select={emptyFunction}
                     dropAction={undefined}
                     isSelected={returnFalse}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b5d9ebd9f..fb949a36d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
+import { DateField } from "../../../../fields/DateField";
 import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
 import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
 import { Id } from "../../../../fields/FieldSymbols";
@@ -17,6 +18,7 @@ import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUp
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
@@ -48,8 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DateField } from "../../../../fields/DateField";
+import Color = require("color");
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -117,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
     @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); }
-    @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.isContentActive() || this.props.isContentActive()); }
+    @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); }
     @computed get fitToContentVals() {
         return {
             bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
@@ -170,6 +171,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getContainerTransform = () => this.cachedGetContainerTransform.copy();
     getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
+    isAnyChildContentActive = () => this.props.isAnyChildContentActive();
     addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox);
@@ -1033,7 +1035,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             docFilters={this.childDocFilters}
             docRangeFilters={this.childDocRangeFilters}
             searchFilterDocs={this.searchFilterDocs}
-            isContentActive={this.isAnnotationOverlay ? this.props.isContentActive : returnFalse}
+            isContentActive={returnFalse}
             isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
             focus={this.focusDocument}
             addDocTab={this.addDocTab}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 82bad971d..faaa887c4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -267,7 +267,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // returns the html audio element
     @computed get audio() {
-        return <audio ref={this.setRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}>
+        return <audio ref={this.setRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
             <source src={this.path} type="audio/mpeg" />
             Not supported.
         </audio>;
@@ -328,6 +328,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             focus={DocUtils.DefaultFocus}
             bringToFront={emptyFunction}
             CollectionView={undefined}
+            isAnyChildContentActive={this.isAnyChildContentActive}
             duration={this.duration}
             playFrom={this.playFrom}
             setTime={this.setAnchorTime}
@@ -337,7 +338,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             ScreenToLocalTransform={this.timelineScreenToLocal}
             Play={this.Play}
             Pause={this.Pause}
-            isContentActive={this.isContentActive}
             playLink={this.playLink}
             PanelWidth={this.timelineWidth}
             PanelHeight={this.timelineHeight}
@@ -345,7 +345,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     render() {
-        const interactive = SnappingManager.GetIsDragging() || this.isContentActive() ? "-interactive" : "";
+        const interactive = SnappingManager.GetIsDragging() || this.props.isContentActive() ? "-interactive" : "";
         return <div className="audiobox-container"
             onContextMenu={this.specificContextMenu}
             onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
@@ -370,7 +370,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             RECORD
                         </button>}
                 </div> :
-                <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }} >
+                <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? "all" : "none" }} >
                     <div className="audiobox-dictation" />
                     <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
                         <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 092823603..9cc4b1f9a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,8 +17,8 @@ import { InkingStroke } from "../InkingStroke";
 import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
-import { FieldViewProps } from "./FieldView";
 import React = require("react");
+import Color = require("color");
 
 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;
@@ -164,6 +164,8 @@ 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 = StrCast(this.layoutDoc.mixBlendMode) as any || (background && Color(background).alpha() !== 1 ? "multiply" : undefined);
         return <div className={"collectionFreeFormDocumentView-container"}
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
@@ -172,7 +174,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 transform: this.transform,
                 transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
                 zIndex: this.ZInd,
-                mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
+                mixBlendMode,
                 display: this.ZInd === -99 ? "none" : undefined
             }} >
             <DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} />
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 153176afc..6708a08ee 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,17 +1,18 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
-import { Doc } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
 import { documentSchema } from '../../../fields/documentSchemas';
 import { createSchema, makeInterface } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { StyleProp } from '../StyleProvider';
 import "./ComparisonBox.scss";
-import { DocumentView } from './DocumentView';
+import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import React = require("react");
 
@@ -71,6 +72,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         delete this.dataDoc[fieldKey];
     }
 
+    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+        if (property === StyleProp.PointerEvents) return "none";
+        return this.props.styleProvider?.(doc, props, property);
+    }
+
     render() {
         const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
         const clearButton = (which: string) => {
@@ -84,6 +90,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
             return whichDoc ? <>
                 <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                    isContentActive={returnFalse}
+                    isDocumentActive={returnFalse}
+                    styleProvider={this.docStyleProvider}
                     Document={whichDoc}
                     DataDoc={undefined}
                     pointerEvents={"none"} />
@@ -102,7 +111,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         };
 
         return (
-            <div className={`comparisonBox${this.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
+            <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
                 {displayBox("after", 1, this.props.PanelWidth() - 3)}
                 <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
                     {displayBox("before", 0, 0)}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bb259da3e..11fb5cdb3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -84,6 +84,7 @@ export interface DocComponentView {
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void;  // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
+    isAnyChildContentActive?: () => boolean; // is any child content of the document active
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     playFrom?: (time: number, endTime?: number) => void;
@@ -182,7 +183,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
+    @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
 
     private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
@@ -778,8 +779,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
     setHeight = (height: number) => this.layoutDoc._height = height;
-    setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
-    isContentActive = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.isContentActive() ? true : false;
+    setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
+    isContentActive = (outsideReaction?: boolean) => {
+        return CurrentUserUtils.SelectedTool !== InkTool.None ||
+            this.props.Document.forceActive ||
+            this.props.isSelected(outsideReaction) ||
+            this._componentView?.isAnyChildContentActive?.() ||
+            this.props.isContentActive() ? true : false;
+    }
     @computed get contents() {
         TraceMobx();
         const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -794,7 +801,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: (this.props.contentPointerEvents as any) || (CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || this.isContentActive() ? "all" : "none"),
+                pointerEvents: (this.props.contentPointerEvents as any) || (this.isContentActive() ? "all" : "none"),
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c4e74ebd2..b392ddb95 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -332,7 +332,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations)
                 this._marqueeing = [e.clientX, e.clientY];
@@ -368,7 +368,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 ScreenToLocalTransform={this.screenToLocalTransform}
                 scaling={returnOne}
                 select={emptyFunction}
-                isContentActive={this.isContentActive}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 23236cf20..341fa5919 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -177,9 +177,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         </>;
         const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
         const curPage = this.Document._curPage || 1;
-        return !this.isContentActive() ? (null) :
+        return !this.props.isContentActive() ? (null) :
             <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true}
-                onPointerDown={e => e.stopPropagation()} style={{ display: this.isContentActive() ? "flex" : "none" }}>
+                onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
                 <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                     <button className="pdfBox-overlayButton" title={searchTitle} />
                     <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
@@ -209,7 +209,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     {this._pageControls ? pageBtns : (null)}
                 </div>
                 <button className="pdfBox-sidebarBtn" title="Toggle Sidebar"
-                    style={{ display: !this.isContentActive() ? "none" : undefined }}
+                    style={{ display: !this.props.isContentActive() ? "none" : undefined }}
                     onPointerDown={this.sidebarBtnDown} >
                     <FontAwesomeIcon icon={"chevron-left"} size="sm" />
                 </button>
@@ -229,7 +229,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     @computed get renderTitleBox() {
-        const classname = "pdfBox" + (this.isContentActive() ? "-interactive" : "");
+        const classname = "pdfBox" + (this.props.isContentActive() ? "-interactive" : "");
         return <div className={classname} >
             <div className="pdfBox-title-outer">
                 <strong className="pdfBox-title" >{this.props.Document.title}</strong>
@@ -268,7 +268,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     dataDoc={this.dataDoc}
                     pdf={this._pdf!}
                     url={this.pdfUrl!.url.pathname}
-                    isContentActive={this.isContentActive}
+                    isContentActive={this.props.isContentActive}
                     anchorMenuClick={this.anchorMenuClick}
                     loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
                     setPdfViewer={this.setPdfViewer}
@@ -291,7 +291,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 sidebarAddDocument={this.sidebarAddDocument}
                 moveDocument={this.moveDocument}
                 removeDocument={this.removeDocument}
-                isContentActive={this.isContentActive}
             />
             {this.settingsPanel()}
         </div>;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d4df30b48..484dec7e2 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -307,7 +307,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
         const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
         return !field ? <div key="loading">Loading</div> :
-            <div className="container" key="container" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }}>
+            <div className="container" key="container" style={{ mixBlendMode: "multiply", pointerEvents: this.props.isContentActive() ? "all" : "none" }}>
                 <div className={`${style}`} style={{ width: "100%", height: "100%", left: "0px" }}>
                     <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
                         style={{ height: "100%", width: "auto", display: "flex", margin: "auto" }}
@@ -321,7 +321,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         Not supported.
                     </video>
                     {!this.audiopath || this.audiopath === field.url.href ? (null) :
-                        <audio ref={this.setAudioRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}>
+                        <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
                             <source src={this.audiopath} type="audio/mpeg" />
                         Not supported.
                     </audio>}
@@ -378,30 +378,30 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private get uIButtons() {
         const curTime = (this.layoutDoc._currentTimecode || 0);
         const nonNativeControls = [
-            <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} placement="bottom">
-                <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} >
+            <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} key="play" placement="bottom">
+                <div className="videoBox-play" onPointerDown={this.onPlayDown} >
                     <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
                 </div>
             </Tooltip>,
-            <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} placement="bottom">
+            <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} key="time" placement="bottom">
                 <div className="videoBox-time" onPointerDown={this.onResetDown} >
                     <span>{formatTime(curTime)}</span>
                     <span style={{ fontSize: 8 }}>{" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")}</span>
                 </div>
             </Tooltip>,
-            <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} placement="bottom">
+            <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} key="full" placement="bottom">
                 <div className="videoBox-full" onPointerDown={this.FullScreen}>
                     <FontAwesomeIcon icon="expand" size="lg" />
                 </div>
             </Tooltip>];
         return <div className="videoBox-ui">
             {[...(VideoBox._nativeControls ? [] : nonNativeControls),
-            <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} placement="bottom">
+            <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} key="snap" placement="bottom">
                 <div className="videoBox-snapshot" onPointerDown={this.onSnapshotDown} >
                     <FontAwesomeIcon icon="camera" size="lg" />
                 </div>
             </Tooltip>,
-            <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} placement="bottom">
+            <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} key="timeline" placement="bottom">
                 <div className="videoBox-timelineButton" onPointerDown={this.onTimelineHdlDown}>
                     <FontAwesomeIcon icon="eye" size="lg" />
                 </div>
@@ -429,7 +429,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         setupMoveUpEvents(this, e,
             action((e: PointerEvent) => {
                 this._clicking = false;
-                if (this.isContentActive()) {
+                if (this.props.isContentActive()) {
                     const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
                     this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
                 }
@@ -438,7 +438,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             () => {
                 this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
                 setTimeout(action(() => this._clicking = false), 500);
-            }, this.isContentActive(), this.isContentActive());
+            }, this.props.isContentActive(), this.props.isContentActive());
     });
 
     onResetDown = (e: React.PointerEvent) => {
@@ -529,12 +529,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 playFrom={this.playFrom}
                 setTime={this.setAnchorTime}
                 playing={this.playing}
+                isAnyChildContentActive={this.isAnyChildContentActive}
                 whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
                 ScreenToLocalTransform={this.timelineScreenToLocal}
                 Play={this.Play}
                 Pause={this.Pause}
-                isContentActive={this.isContentActive}
                 playLink={this.playLink}
                 PanelHeight={this.timelineHeight}
             />
@@ -546,7 +546,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
@@ -570,7 +570,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
     marqueeFitScaling = () => (this.props.scaling?.() || 1) * this.heightPercent / 100;
     marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0];
-    timelineDocFilter = () => ["_timelineLabel:true:x"];
+    timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
     render() {
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
@@ -592,7 +592,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         docFilters={this.timelineDocFilter}
                         select={emptyFunction}
-                        isContentActive={this.isContentActive}
                         scaling={returnOne}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         removeDocument={this.removeDocument}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ae6b90e41..b484ce302 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -401,7 +401,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
@@ -484,7 +484,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             NumCast(this.layoutDoc.nativeWidth)
 
     @computed get content() {
-        return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+        return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
             style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}>
             {this.urlContent}
         </div>;
@@ -529,7 +529,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 docFilters={docFilters || this.props.docFilters}
                 dontRenderDocuments={docFilters ? false : true}
                 select={emptyFunction}
-                isContentActive={returnFalse}
                 ContentScaling={returnOne}
                 bringToFront={emptyFunction}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -539,7 +538,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 childPointerEvents={true}
                 pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return (
-            <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.isContentActive() ? "all" : this.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
+            <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.props.isContentActive() ? "all" : this.props.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
                 <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
                     <base target="_blank" />
                     <div className={"webBox-outerContent"} ref={this._outerRef}
@@ -559,7 +558,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 {renderAnnotations(this.transparentFilter)}
                             </div>
                             {renderAnnotations(this.opaqueFilter)}
-                            {renderAnnotations()}
+                            {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
                             {this.annotationLayer}
                         </div>
                     </div>
@@ -588,10 +587,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
-                    isContentActive={this.isContentActive}
                 />
                 <button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
-                    style={{ display: !this.isContentActive() ? "none" : undefined }}
+                    style={{ display: !this.props.isContentActive() ? "none" : undefined }}
                     onPointerDown={this.sidebarBtnDown} >
                     <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
                 </button>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d1027dfd7..4b1d76d00 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1211,7 +1211,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if ((e.nativeEvent as any).formattedHandled) {
             console.log("handled");
         }
-        if (!(e.nativeEvent as any).formattedHandled && this.isContentActive(true)) {
+        if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) {
             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))));
@@ -1481,7 +1481,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @computed get sidebarHandle() {
         TraceMobx();
         const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
-        return (!annotated && !this.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+        return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
             style={{
                 left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
                 background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
@@ -1504,7 +1504,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
-                    isContentActive={this.isContentActive}
                 /> :
                 <ComponentTag
                     {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
@@ -1517,7 +1516,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     scaleField={this.SidebarKey + "-scale"}
                     isAnnotationOverlay={false}
                     select={emptyFunction}
-                    isContentActive={this.isContentActive}
                     scaling={this.sidebarContentScaling}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.sidebarRemDocument}
@@ -1539,7 +1537,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     render() {
         TraceMobx();
         const selected = this.props.isSelected();
-        const active = this.isContentActive();
+        const active = this.props.isContentActive();
         const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
@@ -1551,7 +1549,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
         return (
             <div className="formattedTextBox-cont"
-                onWheel={e => this.isContentActive() && e.stopPropagation()}
+                onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                 style={{
                     transform: this.props.dontScale ? undefined : `scale(${scale})`,
                     transformOrigin: this.props.dontScale ? undefined : "top left",
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 75e3f81fb..42bec38da 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -69,7 +69,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this._disposer = reaction(() => SelectionManager.Views(),
             selected => {
                 this._showLinkPopup = false;
-                console.log("unmount");
                 AnchorMenu.Instance.fadeOut(true)
             });
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 734d9127c..bc35d2126 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -512,7 +512,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         const renderAnnotations = (docFilters?: () => string[]) =>
             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                 isAnnotationOverlay={true}
-                isContentActive={returnFalse}
                 fieldKey={this.props.fieldKey + "-annotations"}
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelHeight}
@@ -543,7 +542,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     transform: `scale(${this._zoomed})`
                 }}>
                 {renderAnnotations(this.opaqueFilter)}
-                {renderAnnotations()}
+                {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
             </div>
         </div>;
     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 17f41fac8..b09ff93d0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1092,6 +1092,9 @@ export namespace Doc {
             const isTransparent = (color: string) => color !== "" && (Color(color).alpha() !== 1);
             return isTransparent(StrCast(doc[key]));
         }
+        if (typeof value === "string") {
+            value = value.replace(`,${Utils.noRecursionHack}`, "");
+        }
         const fieldVal = doc[key];
         if (Cast(fieldVal, listSpec("string"), []).length) {
             const vals = Cast(fieldVal, listSpec("string"), []);
-- 
cgit v1.2.3-70-g09d2


From c1fb227c364af5defb99adc793e850d681caa36a Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 26 Aug 2021 06:34:54 -0400
Subject: really close

---
 src/client/util/CurrentUserUtils.ts                |  40 +-
 src/client/views/MainView.tsx                      |   2 +
 .../collectionLinearView/CollectionLinearView.scss |   6 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 160 ++++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 494 ++++-----------------
 src/server/index.ts                                |   2 +-
 7 files changed, 227 insertions(+), 479 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 cd2803373..8f75947d1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -51,6 +51,7 @@ interface Button {
     numBtnMax?: number;
     switchToggle?: boolean;
     script?: string;
+    checkResult?: string;
     width?: number;
     list?: string[];
     ignoreClick?: boolean;
@@ -958,29 +959,28 @@ export class CurrentUserUtils {
                 },
                 { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize' },
                 { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor' },
-                { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' },
-                { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' },
-                { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' },
+                { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', checkResult: 'toggleBold(true)' },
+                { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', checkResult: 'toggleItalic(true)' },
+                { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', checkResult: 'toggleUnderline(true)' },
                 // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'},
                 // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'},
                 // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'},
-                { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", ignoreClick: true, script: 'setAlignment("left")' },
-                { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", ignoreClick: true, script: 'setAlignment("center")' },
-                { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", ignoreClick: true, script: 'setAlignment("right")' },
+                { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left")', checkResult: 'setAlignment("left", true)' },
+                { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center")', checkResult: 'setAlignment("center", true)' },
+                { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right")', checkResult: 'setAlignment("right", true)' },
             ];
         return tools;
     }
 
     static inkTools(doc: Doc) {
         const tools: Button[] = [
-            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', script: 'setActiveInkTool("pen" , true)' },
-            { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', script: 'setActiveInkTool("highlighter", true)' },
-            { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', script: 'setActiveInkTool("circle" , true)' },
-            { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', script: 'setActiveInkTool("square" , true)' },
-            { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', script: 'setActiveInkTool("line" , true)' },
+            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', checkResult: 'setActiveInkTool("pen" , true)' },
+            { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', checkResult: 'setActiveInkTool("highlighter", true)' },
+            { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', checkResult: 'setActiveInkTool("circle" , true)' },
+            { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', checkResult: 'setActiveInkTool("square" , true)' },
+            { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', checkResult: 'setActiveInkTool("line" , true)' },
             { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, ignoreClick: true, script: 'setStrokeWidth' },
             { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "minus", ignoreClick: true, script: 'setStrokeColor' },
-
         ];
         return tools;
     }
@@ -995,8 +995,8 @@ export class CurrentUserUtils {
                     width: 50,
                     switchToggle: true,
                     icon: "eye",
-                    ignoreClick: true,
-                    script: 'toggleSchemaShow'
+                    click: 'toggleSchemaShow()',
+                    checkResult: 'toggleSchemaShow(true)'
                 },
             ];
         return tools;
@@ -1029,7 +1029,7 @@ export class CurrentUserUtils {
                 title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
                 script: "setBackgroundColor", hidden: 'selectedDocumentType()'
             }, // Only when a document is selected
-            { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', script: 'toggleOverlay', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
+            { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', checkResult: 'toggleOverlay(true)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
             { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected 
             { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
             { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available
@@ -1042,7 +1042,7 @@ export class CurrentUserUtils {
     static async setupContextMenuButtons(doc: Doc) {
         const docList: Doc[] = [];
 
-        (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => {
+        (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded, checkResult }) => {
             const menuDocList: Doc[] = [];
             if (subMenu) {
                 // default is textTools
@@ -1064,7 +1064,7 @@ export class CurrentUserUtils {
                         tools = CurrentUserUtils.textTools(doc);
                         break;
                 }
-                tools.map(({ title, toolTip, icon, btnType, numBtnType, click, script, width, list, ignoreClick, switchToggle }) => {
+                tools.map(({ title, toolTip, icon, btnType, numBtnType, click, script, width, list, ignoreClick, switchToggle, checkResult }) => {
                     menuDocList.push(Docs.Create.FontIconDocument({
                         _nativeWidth: width ? width : 25,
                         _nativeHeight: 25,
@@ -1085,7 +1085,7 @@ export class CurrentUserUtils {
                         title,
                         switchToggle,
                         color: Colors.WHITE,
-                        backgroundColor: "transparent",
+                        backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any  : "transparent",
                         _dropAction: "alias",
                         _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
                         onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined
@@ -1098,7 +1098,7 @@ export class CurrentUserUtils {
                     linearViewExpandable: true,
                     icon: title,
                     _height: 30,
-                    backgroundColor: "transparent",
+                    backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any  : "transparent",
                     linearViewIsExpanded: expanded ? ComputedField.MakeFunction(expanded) as any : undefined,
                     hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
                 }, menuDocList));
@@ -1276,6 +1276,7 @@ export class CurrentUserUtils {
         doc._raiseWhenDragged = true;
         doc._showLabel = false;
         doc._showMenuLabel = true;
+        doc.textAlign = StrCast(doc.textAlign, "left");
         doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0, 0)");
         doc.activeInkWidth = NumCast(doc.activeInkWidth, 1);
         doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
@@ -1592,7 +1593,6 @@ Scripting.addGlobal(function selectedDocumentType(docType?: DocumentType, colTyp
         const parentDoc: Doc = Cast(selected.context, Doc, null);
         selected = parentDoc;
     }
-    console.log(selected, docType, colType);
     if (selected && docType && selected.type === docType) return false;
     else if (selected && colType && selected.viewType === colType) return false;
     else if (selected && !colType && !docType) return false;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dcdf6b9c3..52dbd451c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -62,6 +62,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
+import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -596,6 +597,7 @@ export class MainView extends React.Component {
             <MarqueeOptionsMenu />
             <OverlayView />
             <TimelineMenu />
+            <RichTextMenu />
             {this.snapLines}
             <div className="mainView-webRef" ref={this.makeWebRef} />
             <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
index 24c9bc9cb..f249eb1f5 100644
--- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
@@ -98,11 +98,7 @@
             transition: transform 0.2s;
             align-items: center;
             justify-content: center;
-            transition: 0.1s;
-
-            &:hover {
-                transform: scale(1.05);
-            }
+            transition: 0.15s;
         }
 
         >input {
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 53cd017b0..e09e15a60 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -3,7 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { StringifyOptions } from 'querystring';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc, StrListCast } from '../../../../fields/Doc';
@@ -11,6 +10,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { createSchema, makeInterface } from '../../../../fields/Schema';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { WebField } from '../../../../fields/URLField';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Scripting } from "../../../util/Scripting";
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -20,14 +20,13 @@ import { CollectionViewType } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
 import { DocComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
+import { GestureOverlay } from '../../GestureOverlay';
 import { Colors } from '../../global/globalEnums';
+import { SetActiveInkColor } from '../../InkingStroke';
 import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
-import { RichTextMenu } from '../formattedText/RichTextMenu';
 import './FontIconBox.scss';
-import { SetActiveInkWidth, SetActiveInkColor } from '../../InkingStroke';
-import { GestureOverlay } from '../../GestureOverlay';
-import { WebField } from '../../../../fields/URLField';
+import { RichTextMenu } from '../formattedText/RichTextMenu';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -241,7 +240,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         let text: string | undefined;
         let dropdown = true;
         let icon: IconProp = "caret-down";
-
+        let noneSelected:boolean = false;
 
         if (script === 'setView') {
             const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
@@ -253,6 +252,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 icon = Doc.toIcon(selected.Document);
             } else {
                 dropdown = false;
+                noneSelected = true;
                 text = "None selected";
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
@@ -298,7 +298,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             <div className={`menuButton ${this.type} ${active}`}
                 style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
                 onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
-                {dropdown ? (null) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+                {dropdown || noneSelected ? (null) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
                 <div className="menuButton-dropdown-header">
                     {text && text[0].toUpperCase() + text.slice(1)}
                 </div>
@@ -395,22 +395,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         // Determine the type of toggle button
         const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
 
-        // Script for running the toggle
-        const script: string = StrCast(this.rootDoc.script);
-
-        // Script for checking the outcome of the toggle
-        let checkScript:string;
-        if (StrCast(this.rootDoc.script).includes("(")){
-            checkScript = StrCast(this.rootDoc.script)
-
-        } else {
-            checkScript = StrCast(this.rootDoc.script) + "(true)";
-        }
-
-        // Function to run the script
-        const runScript = () => ScriptField.MakeScript(script + "()")?.script.run();
-        const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result;
-
         // Colors
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
@@ -423,13 +407,10 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
         if (switchToggle) {
             return (
-                <div className={`menuButton ${this.type} ${'switch'}`}
-                    onClick={runScript}
-                >
+                <div className={`menuButton ${this.type} ${'switch'}`}>
                     <label className="switch">
                         <input type="checkbox"
-                            checked={checkResult}
-                            onChange={runScript}
+                            checked={backgroundColor === Colors.MEDIUM_BLUE}
                         />
                         <span className="slider round"></span>
                     </label>
@@ -438,10 +419,8 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         } else {
             return (
                 <div className={`menuButton ${this.type}`}
-                    style={{ opacity: 1, backgroundColor: checkResult ? Colors.MEDIUM_BLUE : "transparent" }}
-                    onClick={this.layoutDoc.ignoreClick ? runScript : undefined}
-                >
-                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={checkResult ? Colors.LIGHT_GRAY : Colors.LIGHT_GRAY} />
+                    style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}>
+                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
                     {label}
                 </div>
             );
@@ -595,7 +574,8 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo
 Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
     if (checkResult && selected) {
-        return NumCast(selected.Document.z) === 1;
+        if(NumCast(selected.Document.z) === 1) return Colors.MEDIUM_BLUE;
+        else return "transparent";
     }
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
 });
@@ -606,43 +586,109 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
  *      toggleBold
  *      toggleUnderline
  *      toggleItalic
+ *      setAlignment
+ *      toggleBold
+ *      toggleItalic
+ *      toggleUnderline
  **/ 
+Scripting.addGlobal(function toggleBold(){
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+});
+
 
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function setFont(font: string) {
+Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) {
     SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font);
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView);
     Doc.UserDoc()._fontFamily = font;
     return Doc.UserDoc()._fontFamily;
 });
 
+Scripting.addGlobal(function getActiveTextInfo(info:"family" | "size" | "color" | "highlight"){
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
+    switch(info){
+        case "family": return style?.activeColors[0];
+        case "size": return style?.activeSizes[0];
+        case "color": return style?.activeColors[0];
+        case "highlight": return style?.activeHighlights[0];
+    }
+})
+
+Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) {
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+    if (checkResult) {
+        let active:string;
+        if (editorView){
+            active = editorView?.state && RichTextMenu.Instance.getActiveAlignment();
+        } else {
+            active = StrCast(Doc.UserDoc().textAlign); 
+        }
+        if (active === align) return Colors.MEDIUM_BLUE;
+        else return "transparent";
+    }
+    SelectionManager.Views().map(dv => dv.props.Document.textAlign = align);
+    switch(align){
+        case "left":
+            editorView?.state && RichTextMenu.Instance.alignLeft( editorView, editorView.dispatch);
+            break;
+        case "center":
+            editorView?.state && RichTextMenu.Instance.alignCenter(editorView, editorView.dispatch);
+            break;
+        case "right":
+            editorView?.state && RichTextMenu.Instance.alignRight(editorView, editorView.dispatch);
+            break;
+        default:
+            break;
+    }
+    Doc.UserDoc().textAlign = align;
+});
+
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+
     if (checkResult) {
         if (selected) {
-            console.log("[Font color] (selected): " + StrCast(selected._fontColor));
-            return selected._fontColor;
+            console.log("[Font color] (selected): " + StrCast(selected.rootDoc._fontColor));
+            return selected.rootDoc._fontColor;
         } else {
             console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor));
             return Doc.UserDoc()._fontColor;
         }
     }
-    if (selected) selected._fontColor = color;
+
+    if (selected) {
+        selected.rootDoc._fontColor = color;
+        if (color) {
+            editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
+        }
+    }
     Doc.UserDoc()._fontColor = color;
 });
 
-// // toggle: Set overlay status of selected document
-// Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: boolean) {
-//     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-//     if (checkResult) {
-//         if (selected) {
-//             return selected.Document._fontColor;
-//         } else {
-//             return Doc.UserDoc()._fontColor;
-//         }
-//     }
-//     Doc.UserDoc()._fontColor = color;
-// });
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: boolean) {
+    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+
+    if (checkResult) {
+        if (selected) {
+            return selected.Document._fontHighlight;
+        } else {
+            return Doc.UserDoc()._fontHighlight;
+        }
+    }
+    if (selected) {
+        selected.rootDoc._fontColor = color;
+        if (color) {
+            editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
+        }
+    }
+    Doc.UserDoc()._fontHighlight = color;
+});
 
 
 
@@ -652,12 +698,15 @@ Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) {
         const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10);
         return size;
     }
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    editorView?.state && RichTextMenu.Instance.setFontSize(Number(size), editorView);
     Doc.UserDoc()._fontSize = size + "px";
 });
 
 Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
     if (checkResult) {
-        return Doc.UserDoc().bold;
+        if (Doc.UserDoc().bold) return Colors.MEDIUM_BLUE;
+        else return "transparent";
     }
     SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.italic = !dv.props.Document.italic);
     Doc.UserDoc().bold = !Doc.UserDoc().bold;
@@ -666,7 +715,8 @@ Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
 
 Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
     if (checkResult) {
-        return Doc.UserDoc().underline;
+        if (Doc.UserDoc().underline) return Colors.MEDIUM_BLUE;
+        else return "transparent";
     }
     SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.italic = !dv.props.Document.italic);
     Doc.UserDoc().underline = !Doc.UserDoc().underline;
@@ -675,7 +725,8 @@ Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
 
 Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
     if (checkResult) {
-        return Doc.UserDoc().italic;
+        if (Doc.UserDoc().italic) return Colors.MEDIUM_BLUE;
+        else return "transparent";
     }
     SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.italic = !dv.props.Document.italic);
     Doc.UserDoc().italic = !Doc.UserDoc().italic;
@@ -693,7 +744,8 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
 
 Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) {
     if (checkResult) {
-        return Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool;
+        if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) return Colors.MEDIUM_BLUE;
+        else return "transparent";
     }
     if (tool === "circle") {
         Doc.UserDoc().activeInkTool = "pen";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d1027dfd7..f4053d69f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -214,7 +214,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     setupAnchorMenu = () => {
         AnchorMenu.Instance.Status = "marquee";
         AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
-            this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
+            this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch);
             return undefined;
         });
         AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index c081b8e26..e32eccb62 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -37,11 +37,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
 
     public _brushMap: Map<string, Set<Mark>> = new Map();
-    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
-    private fontColors: (string | undefined)[];
-    private highlightColors: (string | undefined)[];
 
     @observable private collapsed: boolean = false;
     @observable private boldActive: boolean = false;
@@ -76,70 +71,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         this._canFade = false;
         //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
         runInAction(() => this.Pinned = true);
-
-        this.fontSizeOptions = [
-            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48px", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72px", command: this.changeFontSize },
-            { mark: null, title: "", label: "...", command: unimplementedFunction, hidden: true },
-            { mark: null, title: "", label: "13px", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
-        ];
-
-        this.fontFamilyOptions = [
-            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
-        ];
-
-        this.listTypeOptions = [
-            { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },
-            //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
-        ];
-
-        this.fontColors = [
-            DarkPastelSchemaPalette.get("pink2"),
-            DarkPastelSchemaPalette.get("purple4"),
-            DarkPastelSchemaPalette.get("bluegreen1"),
-            DarkPastelSchemaPalette.get("yellow4"),
-            DarkPastelSchemaPalette.get("red2"),
-            DarkPastelSchemaPalette.get("bluegreen7"),
-            DarkPastelSchemaPalette.get("bluegreen5"),
-            DarkPastelSchemaPalette.get("orange1"),
-            "#757472",
-            "#000"
-        ];
-
-        this.highlightColors = [
-            PastelSchemaPalette.get("pink2"),
-            PastelSchemaPalette.get("purple4"),
-            PastelSchemaPalette.get("bluegreen1"),
-            PastelSchemaPalette.get("yellow4"),
-            PastelSchemaPalette.get("red2"),
-            PastelSchemaPalette.get("bluegreen7"),
-            PastelSchemaPalette.get("bluegreen5"),
-            PastelSchemaPalette.get("orange1"),
-            "white",
-            "transparent"
-        ];
     }
 
     componentDidMount() {
@@ -348,102 +279,38 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         });
     }
 
-    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
-        const self = this;
-        function onClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.TextView?.endUndoTypingBatch();
-            UndoManager.RunInBatch(() => {
-                self.view && command && command(self.view.state, self.view.dispatch, self.view);
-                self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
-            }, "rich text menu command");
-            self.setActiveMarkButtons(self.getActiveMarksOnSelection());
-        }
-
-        return (
-            <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom">
-                <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}>
-                    <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
-                </button>
-            </Tooltip>
-        );
-    }
-
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => void): JSX.Element {
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
-
-        const self = this;
-        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
-            e.stopPropagation();
-            e.preventDefault();
-            self.TextView?.endUndoTypingBatch();
-            UndoManager.RunInBatch(() => {
-                options.forEach(({ label, mark, command }) => {
-                    if (e.target.value === label && mark) {
-                        if (!self.TextView?.props.isSelected(true)) {
-                            switch (mark.type) {
-                                case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
-                                case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "px"); break;
-                            }
-                        }
-                        else self.view && mark && command(mark, self.view);
-                    }
-                });
-            }, "text mark dropdown");
-        }
-
-        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
-            <select onChange={onChange} value={activeOption}>{items}</select>
-        </Tooltip>;
-    }
-
-    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
-        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
 
-        const self = this;
-        function onChange(val: string) {
-            self.TextView.endUndoTypingBatch();
-            options.forEach(({ label, node, command }) => {
-                if (val === label && node) {
-                    if (self.TextView.props.isSelected(true)) {
-                        UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
-                        setter(val);
-                    }
-                }
-            });
-        }
-
-        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
-            <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select>
-        </Tooltip>;
-    }
-
-    changeFontSize = (mark: Mark, view: EditorView) => {
-        const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize });
+    setFontSize = (size:number, view: EditorView) => {
+        const fmark = view.state.schema.marks.pFontSize.create({ fontSize: size });
         this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
         view.focus();
         this.updateMenu(view, undefined, this.props);
     }
 
-    changeFontFamily = (mark: Mark, view: EditorView) => {
-        const fmark = view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family });
+    setFontFamily = (family:string, view: EditorView) => {
+        const fmark = view.state.schema.marks.pFontFamily.create({ family: family });
         this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
         view.focus();
         this.updateMenu(view, undefined, this.props);
     }
 
+    setHighlight(color: String, view: EditorView, dispatch: any) {
+        const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
+        if (view.state.selection.empty) return false;
+        view.focus();
+        this.setMark(highlightMark, view.state, dispatch, false);
+    }
+
+    setColor(color: String, view: EditorView, dispatch: any) {
+        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+        if (view.state.selection.empty) {
+            dispatch(view.state.tr.addStoredMark(colorMark));
+            return false;
+        }
+        this.setMark(colorMark, view.state, dispatch, true);
+        view.focus();
+    }
+
     // TODO: remove doesn't work
     //remove all node type and apply the passed-in one to the selected text
     changeListType = (nodeType: Node | undefined) => {
@@ -491,23 +358,24 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
-    alignCenter = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
+    alignCenter = (view: EditorView, dispatch: any) => {
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "center", dispatch);
     }
-    alignLeft = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
+    alignLeft = (view: EditorView, dispatch: any) => {
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "left", dispatch);
     }
-    alignRight = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);
+    alignRight = (view: EditorView, dispatch: any) => {
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "right", dispatch);
     }
 
-    alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
-        var tr = state.tr;
-        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+    alignParagraphs(view: EditorView, align: "left" | "right" | "center", dispatch: any) {
+        var tr = view.state.tr;
+        view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
             if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                 tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
                 return false;
             }
+            view.focus();
             return true;
         });
         dispatch?.(tr);
@@ -598,47 +466,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     }
     _brushNameRef = React.createRef<HTMLInputElement>();
 
-    createBrushButton() {
-        const self = this;
-        const onBrushClick = (e: React.MouseEvent) => {
-            e.preventDefault();
-            e.stopPropagation();
-            self.TextView.endUndoTypingBatch();
-            UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush");
-        };
-
-        let label = "Stored marks: ";
-        if (this.brushMarks && this.brushMarks.size > 0) {
-            this.brushMarks.forEach((mark: Mark) => {
-                const markType = mark.type;
-                label += markType.name;
-                label += ", ";
-            });
-            label = label.substring(0, label.length - 2);
-        } else {
-            label = "No marks are currently stored";
-        }
-
-        //onPointerDown={onBrushClick}
-
-        const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom">
-            <button className="antimodeMenu-button" onClick={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
-                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
-            </button>
-        </Tooltip>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>{label}</p>
-                <button onPointerDown={this.clearBrush}>Clear brush</button>
-                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress} />
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} openDropdownOnButton={false} dropdownContent={dropdownContent} />
-        );
-    }
-
     @action
     clearBrush() {
         RichTextMenu.Instance.brushMarks = new Set();
@@ -667,123 +494,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         }
     }
 
-    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
-    @action setActiveColor(color: string) { this.activeFontColor = color; }
     get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
     get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
 
-    createColorButton() {
-        const self = this;
-        function onColorClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.TextView.endUndoTypingBatch();
-            if (self.view) {
-                UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
-                self.view.focus();
-                self.updateMenu(self.view, undefined, self.props);
-            }
-        }
-        function changeColor(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.setActiveColor(color);
-            self.TextView.endUndoTypingBatch();
-            if (self.view) {
-                UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
-                self.view.focus();
-                self.updateMenu(self.view, undefined, self.props);
-            }
-        }
-
-        // onPointerDown={onColorClick}
-        const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom">
-            <button className="antimodeMenu-button color-preview-button">
-                <FontAwesomeIcon icon="palette" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
-            </button>
-        </Tooltip>;
+    
 
-        const dropdownContent =
-            <div className="dropdown" >
-                <p>Change font color:</p>
-                <div className="color-wrapper">
-                    {this.fontColors.map(color => {
-                        if (color) {
-                            return this.activeFontColor === color ?
-                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
-        );
-    }
-
-    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
-        if (state.selection.empty) {
-            dispatch(state.tr.addStoredMark(colorMark));
-            return false;
-        }
-        this.setMark(colorMark, state, dispatch, true);
-    }
-
-    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
     @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
 
-    createHighlighterButton() {
-        const self = this;
-        function onHighlightClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.TextView.endUndoTypingBatch();
-            UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher");
-        }
-        function changeHighlight(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.setActiveHighlight(color);
-            self.TextView.endUndoTypingBatch();
-            UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
-        }
-
-        //onPointerDown={onHighlightClick}
-        const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom">
-            <button className="antimodeMenu-button color-preview-button" key="highilghter-button" >
-                <FontAwesomeIcon icon="highlighter" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
-            </button>
-        </Tooltip>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>Change highlight color:</p>
-                <div className="color-wrapper">
-                    {this.highlightColors.map(color => {
-                        if (color) {
-                            return this.activeHighlightColor === color ?
-                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
-        );
-    }
 
-    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
-    }
-
-    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
     @action setCurrentLink(link: string) { this.currentLink = link; }
 
     createLinkButton() {
@@ -922,93 +640,73 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         return ref_node;
     }
 
-    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
-    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
-
-    @action
-    toggleMenuPin = (e: React.MouseEvent) => {
-        Doc.UserDoc()["menuRichText-pinned"] = this.Pinned = !this.Pinned;
-        if (!this.Pinned) {
-            this.fadeOut(true);
-        }
-    }
-
-    @action
-    protected toggleCollapse = (e: React.MouseEvent) => {
-        this.collapsed = !this.collapsed;
-        setTimeout(() => {
-            const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
-            RichTextMenu.Instance.jumpTo(x, this._top, true);
-        }, 0);
-    }
-
     render() {
-        TraceMobx();
-        const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
-            //!this.collapsed ? this.getDragger() : (null),
-            // !this.Pinned ? (null) : <div key="frag1"> {[
-            //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-            //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-            //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-            //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-            //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-            //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-            //     <div className="richTextMenu-divider" key="divider" />
-            // ]}</div>,
-            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-            this.createColorButton(),
-            this.createHighlighterButton(),
-            this.createLinkButton(),
-            this.createBrushButton(),
-            <div className="collectionMenu-divider" key="divider 2" />,
-            this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
-            this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
-            this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
-            this.createButton("indent", "Inset More", undefined, this.insetParagraph),
-            this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
-            this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
-            this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
-        ]}</div>;
-
-        const row2 = <div className="antimodeMenu-row row-2" key="row2">
-            {this.collapsed ? this.getDragger() : (null)}
-            <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
-                <div className="collectionMenu-divider" key="divider 3" />
-                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
-                    this.activeFontSize = val;
-                    SelectionManager.Views().map(dv => dv.props.Document._fontSize = val);
-                })),
-                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
-                    this.activeFontFamily = val;
-                    SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
-                })),
-                <div className="collectionMenu-divider" key="divider 4" />,
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
-                this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
-                this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
-                this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
-                ]}
-            </div>
-            {/* <div key="collapser">
-                {<div key="collapser">
-                    <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
-                    </button>
-                </div> }
-                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-                </button>
-            </div> */}
-        </div>;
+        // TraceMobx();
+        // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+        //     //!this.collapsed ? this.getDragger() : (null),
+        //     // !this.Pinned ? (null) : <div key="frag1"> {[
+        //     //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+        //     //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+        //     //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+        //     //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+        //     //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+        //     //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+        //     //     <div className="richTextMenu-divider" key="divider" />
+        //     // ]}</div>,
+        //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+        //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+        //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+        //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+        //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+        //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+        //     this.createColorButton(),
+        //     this.createHighlighterButton(),
+        //     this.createLinkButton(),
+        //     this.createBrushButton(),
+        //     <div className="collectionMenu-divider" key="divider 2" />,
+        //     this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
+        //     this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
+        //     this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
+        //     this.createButton("indent", "Inset More", undefined, this.insetParagraph),
+        //     this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
+        //     this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
+        //     this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
+        // ]}</div>;
+
+        // const row2 = <div className="antimodeMenu-row row-2" key="row2">
+        //     {this.collapsed ? this.getDragger() : (null)}
+        //     <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
+        //         <div className="collectionMenu-divider" key="divider 3" />
+        //         {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
+        //             this.activeFontSize = val;
+        //             SelectionManager.Views().map(dv => dv.props.Document._fontSize = val);
+        //         })),
+        //         this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
+        //             this.activeFontFamily = val;
+        //             SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
+        //         })),
+        //         <div className="collectionMenu-divider" key="divider 4" />,
+        //         this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
+        //         this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
+        //         this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
+        //         this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
+        //         ]}
+        //     </div>
+        //     {/* <div key="collapser">
+        //         {<div key="collapser">
+        //             <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
+        //                 <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
+        //             </button>
+        //         </div> }
+        //         <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+        //             <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+        //         </button>
+        //     </div> */}
+        // </div>;
 
         return (
-            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} >
-                {this.getElementWithRows([row1, row2], 2, false)}
+            <div className="richTextMenu">
+                {/* {this.getElementWithRows([row1, row2], 2, false)} */}
             </div>
         );
     }
diff --git a/src/server/index.ts b/src/server/index.ts
index 9687c3b23..f8c32103b 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -16,6 +16,7 @@ import UserManager from './ApiManagers/UserManager';
 import UtilManager from './ApiManagers/UtilManager';
 import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
 import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
+import { DashSessionAgent } from "./DashSession/DashSessionAgent";
 import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
 import { DashUploadUtils } from './DashUploadUtils';
 import { Database } from './database';
@@ -23,7 +24,6 @@ import { Logger } from "./ProcessFactory";
 import RouteManager, { Method, PublicHandler } from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
 import initializeServer, { resolvedPorts } from './server_Initialization';
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
 
 export const AdminPriviliges: Map<string, boolean> = new Map();
 export const onWindows = process.platform === "win32";
-- 
cgit v1.2.3-70-g09d2


From f915c08c71d1384add30db49e4c74461d3cf0dcd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 30 Aug 2021 16:39:26 -0400
Subject: reverted textboxes to not flicker when they have bullets

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a9beb67de..f45b9de7a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1451,11 +1451,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
                     if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
                         setScrollHeight();
-                        setTimeout(() => {
-                            proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                            scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-                            scrollHeight && setScrollHeight();
-                        }, 10);
+                        // setTimeout(() => {
+                        //     proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+                        //     scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                        //     scrollHeight && setScrollHeight();
+                        // }, 10);
                     } 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 12d8267533d9b646247914e965b3cf7c32019e4b Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 2 Sep 2021 04:45:01 -0400
Subject: Many updates

- Fixed image uploads so it clones rather than makes an alias
- Updated context menu
- Removed tools panel from novice mode in favour of ":" menu
- Added explainers to menus in novice mode
- Re-added the trails button
- Changed UI for text, PDF & websites so that it is more consistent
---
 src/client/documents/Documents.ts                  |  52 +++++----
 src/client/util/CurrentUserUtils.ts                |  93 ++++++++--------
 src/client/views/ContextMenu.scss                  |   2 +-
 src/client/views/ContextMenuItem.tsx               |   4 +-
 src/client/views/MainViewModal.scss                |   3 +-
 src/client/views/PropertiesButtons.scss            |  41 +++++--
 src/client/views/PropertiesButtons.tsx             |  53 +++++----
 src/client/views/SidebarAnnos.tsx                  |   3 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionStackingView.scss  |  24 +++++
 .../views/collections/CollectionStackingView.tsx   | 119 +++++++++++++++------
 .../views/collections/CollectionTreeView.tsx       |  95 ++++++++++++----
 src/client/views/collections/TabDocView.tsx        |   3 +-
 src/client/views/collections/TreeView.tsx          |   5 +-
 .../collectionLinearView/CollectionLinearView.scss |   6 +-
 .../collectionSchema/CollectionSchemaHeaders.tsx   |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/PDFBox.scss                 |  16 ++-
 src/client/views/nodes/PDFBox.tsx                  |  13 ++-
 src/client/views/nodes/WebBox.scss                 |   9 +-
 src/client/views/nodes/WebBox.tsx                  |  13 ++-
 src/client/views/nodes/button/FontIconBox.scss     |  29 ++++-
 src/client/views/nodes/button/FontIconBox.tsx      |  15 +--
 .../nodes/formattedText/FormattedTextBox.scss      |  21 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  16 ++-
 src/client/views/search/SearchBox.tsx              |   2 +-
 src/client/views/topbar/TopBar.scss                |   4 +-
 src/client/views/topbar/TopBar.tsx                 |   3 +-
 31 files changed, 460 insertions(+), 202 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 c4a713dc6..bd247bd01 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -59,6 +59,7 @@ import { WebBox } from "../views/nodes/WebBox";
 import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -272,7 +273,12 @@ export class DocumentOptions {
     treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
-    treeViewShowClearButton?: boolean; // whether a clear button should be displayed 
+
+    // Action Button
+    buttonMenu?: boolean; // whether a action button should be displayed 
+    buttonMenuDoc?: Doc;
+    explainer?:string;
+
     treeViewOpenIsTransient?: boolean; // ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view
     _treeViewOpen?: boolean; // whether this document is expanded in a tree view  (note: need _ and regular versions since this can be specified for both proto and layout docs)
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
@@ -1165,7 +1171,7 @@ export namespace DocUtils {
 
     export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false): void {
         !simpleMenu && ContextMenu.Instance.addItem({
-            description: "Add Note ...",
+            description: "Quick Notes",
             subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
                 description: ":" + StrCast(note.title),
                 event: undoBatch((args: { x: number, y: number }) => {
@@ -1178,12 +1184,12 @@ export namespace DocUtils {
                     textDoc[textDoc.layoutKey] = note;
                     docTextAdder(textDoc);
                 }),
-                icon: "eye"
+                icon: StrCast(note.icon) as IconProp
             })) as ContextMenuProps[],
-            icon: "eye"
+            icon: "sticky-note"
         });
-        ContextMenu.Instance.addItem({
-            description: ":=math", event: () => {
+        const math:ContextMenuProps = ({
+            description: ":Math", event: () => {
                 const created = Docs.Create.EquationDocument();
                 if (created) {
                     created.author = Doc.CurrentUserEmail;
@@ -1194,25 +1200,27 @@ export namespace DocUtils {
                     EquationBox.SelectOnLoad = created[Id];
                     docAdder?.(created);
                 }
-            }, icon: "compress-arrows-alt"
+            }, icon: "calculator"
         });
+        const documentList:ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
+            description: ":" + StrCast(dragDoc.title),
+            event: undoBatch((args: { x: number, y: number }) => {
+                const newDoc = Doc.copyDragFactory(dragDoc);
+                if (newDoc) {
+                    newDoc.author = Doc.CurrentUserEmail;
+                    newDoc.x = x;
+                    newDoc.y = y;
+                    if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
+                    docAdder?.(newDoc);
+                }
+            }),
+            icon: Doc.toIcon(dragDoc),
+        })) as ContextMenuProps[]; 
+        documentList.push(math)
         ContextMenu.Instance.addItem({
             description: "Create document",
-            subitems: DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
-                description: ":" + StrCast(dragDoc.title),
-                event: undoBatch((args: { x: number, y: number }) => {
-                    const newDoc = Doc.copyDragFactory(dragDoc);
-                    if (newDoc) {
-                        newDoc.author = Doc.CurrentUserEmail;
-                        newDoc.x = x;
-                        newDoc.y = y;
-                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
-                        docAdder?.(newDoc);
-                    }
-                }),
-                icon: Doc.toIcon(dragDoc),
-            })) as ContextMenuProps[],
-            icon: "eye"
+            subitems: documentList,
+            icon: "file"
         });
     }// applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
     export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f12ac02f3..f6ec74482 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -84,7 +84,7 @@ export class CurrentUserUtils {
                 title: "NEW MOBILE BUTTON",
                 onClick: undefined,
             },
-                [this.ficon({
+                [this.createToolButton({
                     ignoreClick: true,
                     icon: "mobile",
                     btnType: ButtonType.ToolButton,
@@ -92,7 +92,7 @@ export class CurrentUserUtils {
                 }),
                 this.mobileTextContainer({},
                     [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
-            doc["template-mobile-button"] = CurrentUserUtils.ficon({
+            doc["template-mobile-button"] = CurrentUserUtils.createToolButton({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton,
             });
@@ -107,7 +107,7 @@ export class CurrentUserUtils {
                 { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
             );
             slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
-            doc["template-button-slides"] = CurrentUserUtils.ficon({
+            doc["template-button-slides"] = CurrentUserUtils.createToolButton({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card",
                 btnType: ButtonType.ToolButton
@@ -154,7 +154,7 @@ export class CurrentUserUtils {
             };
             linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
 
-            doc["template-button-link"] = CurrentUserUtils.ficon({
+            doc["template-button-link"] = CurrentUserUtils.createToolButton({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true,
                 btnType: ButtonType.ToolButton
@@ -186,7 +186,7 @@ export class CurrentUserUtils {
             const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true });
             box.isTemplateDoc = makeTemplate(box, true, "switch");
 
-            doc["template-button-switch"] = CurrentUserUtils.ficon({
+            doc["template-button-switch"] = CurrentUserUtils.createToolButton({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true,
                 btnType: ButtonType.ToolButton
@@ -236,7 +236,7 @@ export class CurrentUserUtils {
             short.title = "A Short Description";
             long.title = "Long Description";
 
-            doc["template-button-detail"] = CurrentUserUtils.ficon({
+            doc["template-button-detail"] = CurrentUserUtils.createToolButton({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(detailView) as any as Doc,
                 title: "detailView",
@@ -275,7 +275,7 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc) {
         if (doc["template-note-Note"] === undefined) {
             const noteView = Docs.Create.TextDocument("", {
-                title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true,
+                title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note",
                 _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
             });
             noteView.isTemplateDoc = makeTemplate(noteView, true, "Note");
@@ -283,7 +283,7 @@ export class CurrentUserUtils {
         }
         if (doc["template-note-Idea"] === undefined) {
             const noteView = Docs.Create.TextDocument("", {
-                title: "text", backgroundColor: "pink", system: true,
+                title: "text", backgroundColor: "pink", system: true, icon: "lightbulb",
                 _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
             });
             noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea");
@@ -291,7 +291,7 @@ export class CurrentUserUtils {
         }
         if (doc["template-note-Topic"] === undefined) {
             const noteView = Docs.Create.TextDocument("", {
-                title: "text", backgroundColor: "lightblue", system: true,
+                title: "text", backgroundColor: "lightblue", system: true, icon: "book-open",
                 _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
             });
             noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic");
@@ -467,7 +467,7 @@ export class CurrentUserUtils {
             ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0;
         }
         if (doc.emptyComparison === undefined) {
-            doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
+            doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
         }
         if (doc.emptyScript === undefined) {
             doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List<string>(["system"]) });
@@ -512,7 +512,7 @@ export class CurrentUserUtils {
             { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc },
             { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc },
             { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
-            { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc, noviceMode: true },
+            { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc },
             { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
             { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
             { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
@@ -561,7 +561,7 @@ export class CurrentUserUtils {
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
                 title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
-                _forceActive: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
+                _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true,
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
             }));
         } else {
@@ -574,29 +574,31 @@ export class CurrentUserUtils {
         return [
             { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
             { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' },
-            { title: "My Files", target: Cast(doc.myFilesystem, Doc, null), icon: "file", click: 'selectMainMenu(self)' },
-            { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' },
-            { title: "Import", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
+            { title: "File Manager", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' },
+            { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
+            { title: "Uploads", target: Cast(doc.myUploadDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
             { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
             { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
             { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
-            { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' },
+            { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
         ];
     }
 
     static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         if (doc.menuStack === undefined) {
             await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId);  // sets up the right sidebar collection for mobile upload documents and sharing
-            const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) =>
+            const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
                 Docs.Create.FontIconDocument({
                     icon,
                     btnType: ButtonType.MenuButton,
                     _stayInCollection: true,
                     _hideContextMenu: true,
+                    _chromeHidden: true,
                     system: true,
                     dontUndo: true,
                     title,
                     target,
+                    hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
                     _dropAction: "alias",
                     _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
                     _width: 60,
@@ -611,8 +613,6 @@ export class CurrentUserUtils {
                     this.searchBtn = menuBtn;
                 }
             });
-            // hack -- last button is assumed to be the userDoc
-            menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()");
 
             menuBtns.forEach(menuBtn => {
                 if (menuBtn.title === "Search") {
@@ -684,7 +684,7 @@ export class CurrentUserUtils {
                 onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
                 backgroundColor: data.backgroundColor, system: true
             },
-                [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
+                [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
         );
     }
 
@@ -820,6 +820,7 @@ export class CurrentUserUtils {
             const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
             (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
             (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "share", "times"]);
             // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
             // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
         }
@@ -860,18 +861,21 @@ export class CurrentUserUtils {
     }
 
     static setupRecentlyClosedDocs(doc: Doc) {
-        // setup Recently Closed library item
         if (doc.myRecentlyClosedDocs === undefined) {
-            const clearDocs = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript(`getProto(self).data = new List([])`), toolTip: "Empty", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.TextButton, _width: 200, buttonText: "Empty Recently Closed", icon: "trash", system: true });
-            doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([clearDocs], {
-                title: "My Recently Closed", _showTitle: "title", treeViewShowClearButton: true, childHideLinkButton: true,
+            const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
+            const clearDocsButton:Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
+            doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
+                title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
                 treeViewTruncateTitleWidth: 150, ignoreClick: true,
-                _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
+                _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
+                explainer: "Documents that you close will appear here so you can easily access them again. You can choose to clear this menu in which case you might lose these documents."
+
             }));
-            const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
             (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
-            (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
+            (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Empty recently closed"]);
+            (doc.myRecentlyClosedDocs as any as Doc).contextMenuIcons = new List<string>(["trash"]);
+
         }
     }
 
@@ -916,7 +920,7 @@ export class CurrentUserUtils {
     static async setupSidebarButtons(doc: Doc) {
         CurrentUserUtils.setupSidebarContainer(doc);
         await CurrentUserUtils.setupToolsBtnPanel(doc);
-        CurrentUserUtils.setupImportSidebar(doc);
+        CurrentUserUtils.setupUploadSidebar(doc);
         CurrentUserUtils.setupDashboards(doc);
         CurrentUserUtils.setupPresentations(doc);
         CurrentUserUtils.setupFilesystem(doc);
@@ -930,17 +934,17 @@ export class CurrentUserUtils {
         _lockedPosition: true, system: true, flexDirection: "row"
     })) as any as Doc
 
-    static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
-        ...opts, _dropAction: "alias", _removeDropProperties: new List<string>(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true
+    static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
+        ...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List<string>(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true
     })) as any as Doc
 
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupDockedButtons(doc: Doc) {
         if (doc["dockedBtn-undo"] === undefined) {
-            doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true });
+            doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("undo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true });
         }
         if (doc["dockedBtn-redo"] === undefined) {
-            doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true });
+            doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("redo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true });
         }
         if (doc.dockedBtns === undefined) {
             doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
@@ -1039,7 +1043,7 @@ export class CurrentUserUtils {
             // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected 
             { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
             { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available
-            // { title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
+            { title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
             { title: "Schema", type: "schemaTools", subMenu: true, hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected
         ];
     }
@@ -1172,13 +1176,15 @@ export class CurrentUserUtils {
             }
             doc.myLinkDatabase = new PrefetchProxy(linkDocs);
         }
+        // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document
         if (doc.mySharedDocs === undefined) {
             let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer");
             if (!sharedDocs) {
                 sharedDocs = Docs.Create.TreeDocument([], {
                     title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
-                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
+                    _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
                     _chromeHidden: true, boxShadow: "0 0",
+                    explainer: "All of the documents that you receive will appear here. They must be shared by other users. If you receive a Dashboard you can add it to your Dashboards by right clicking and clicking 'Add to Dashboards'"
                 }, sharingDocumentId + "outer", sharingDocumentId);
                 (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
             }
@@ -1189,18 +1195,21 @@ export class CurrentUserUtils {
                 sharedDocs.childContextMenuFilters = new List<ScriptField>([dashboardFilter!,]);
                 sharedDocs.childContextMenuScripts = new List<ScriptField>([addToDashboards!,]);
                 sharedDocs.childContextMenuLabels = new List<string>(["Add to Dashboards",]);
+                sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]);
+
             }
             doc.mySharedDocs = new PrefetchProxy(sharedDocs);
         }
     }
 
     // Import sidebar is where shared documents are contained
-    static setupImportSidebar(doc: Doc) {
-        if (doc.myImportDocs === undefined) {
-            const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Upload from computer", _width: 100, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.TextButton, buttonText: "Upload from computer", icon: "upload", system: true });
-            doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([newUpload], {
-                title: "My Uploads", _forceActive: true, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
-                childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
+    static setupUploadSidebar(doc: Doc) {
+        if (doc.myUploadDocs === undefined) {
+            const newUploadButton:Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Upload from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.ClickButton, buttonText: "Upload", icon: "upload", system: true });
+            doc.myUploadDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
+                title: "My Uploads", _forceActive: true, buttonMenu: true, buttonMenuDoc: newUploadButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
+                childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
+                explainer: "All of the documents that are imported into Dash will go here and stay here unless they are explicitly removed."
             }));
         }
     }
@@ -1303,7 +1312,7 @@ export class CurrentUserUtils {
         doc.filterDocCount = 0;
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupDocTemplates(doc); // sets up the template menu of templates
-        this.setupImportSidebar(doc); // sets up the import sidebar
+        this.setupUploadSidebar(doc); // sets up the import sidebar
         this.setupSearchSidebar(doc); // sets up the search sidebar
         this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
         this.setupOverlays(doc);  // documents in overlay layer
@@ -1430,7 +1439,7 @@ export class CurrentUserUtils {
                     }
                 }
             } else if (input.files && input.files.length !== 0) {
-                const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
+                const importDocs = Cast(Doc.UserDoc().myUploadDocs, Doc, null);
                 const disposer = OverlayView.ShowSpinner();
                 DocListCastAsync(importDocs.data).then(async list => {
                     const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 41a6dc569..47ae0424b 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -130,7 +130,7 @@
 }
 
 .contextMenu-inlineMenu {
-    border-top: solid 1px;
+    // border-top: solid 1px; //TODO:glr clean
 }
 
 .contextMenu-item:hover {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 168fcb888..c3921d846 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -116,12 +116,12 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
                     onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
                     {this.props.icon ? (
-                        <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center" }}>
+                        <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
                             <FontAwesomeIcon icon={this.props.icon} size="sm" />
                         </span>
                     ) : null}
                     <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
-                        style={{ alignItems: "center" }} >
+                        style={{ alignItems: "center", alignSelf: "center" }} >
                         {this.props.description}
                         <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
                     </div>
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index 03cb5cc84..0648e31c5 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -6,6 +6,7 @@
     height: 100%;
 
     .dialogue-box {
+        padding: 10px;
         position: absolute;
         z-index: 1000;
         text-align: center;
@@ -13,7 +14,7 @@
         align-self: center;
         align-content: center;
         background: white;
-        border-radius: 10px;
+        // border-radius: 10px;
         box-shadow: #00000044 5px 5px 10px;
         transform: translate(-50%, -50%);
         top: 50%;
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index 484522bc7..77686a710 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -67,10 +67,12 @@ $linkGap : 3px;
 }
 
 .onClickFlyout-editScript {
+    cursor: pointer;
     text-align: center;
-    border: 0.5px solid grey;
+    margin-top: 5px;
+    border: 0.5px solid $medium-gray;
     background-color: rgb(230, 230, 230);
-    border-radius: 9px;
+    border-radius: 5px;
     padding: 4px;
 }
 
@@ -84,9 +86,32 @@ $linkGap : 3px;
     margin-bottom: 8px;
 }
 
+.propertiesButton-dropdownList {
+    width: 100%;
+    height: fit-content;
+    top: 100%;
+    z-index: 21;
+
+    .list-item {
+        cursor: pointer;
+        color: $black;
+        width: 100%;
+        height: 25px;
+        font-weight: 400;
+        display: flex;
+        justify-content: left;
+        align-items: center;
+        padding-left: 5px;
+    }
+
+    .list-item:hover {
+        background-color: lightgrey;
+    }
+}
+
 .propertiesButtons-title {
-    background: #121721;
-    color: white;
+    background: $dark-gray;
+    color: $white;
     font-size: 6px;
     width: 37px;
     padding: 3px;
@@ -111,17 +136,11 @@ $linkGap : 3px;
     margin-left: 4px;
 
     &:hover {
-        background: $medium-gray;
-        transform: scale(1.05);
+        filter:brightness(0.85);
         cursor: pointer;
     }
 }
 
-.propertiesButtons-linker:hover {
-    cursor: pointer;
-    transform: scale(1.05);
-}
-
 
 @-moz-keyframes spin {
     100% {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index dcab9bc1e..4fde1124f 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -15,6 +15,7 @@ import { InkingStroke } from './InkingStroke';
 import { DocumentView } from './nodes/DocumentView';
 import './PropertiesButtons.scss';
 import React = require("react");
+import { Colors } from "./global/globalEnums";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -128,11 +129,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @undoBatch
     @action
-    handleOptionChange = (e: any) => {
-        this.selectedDoc && (this.selectedDoc.onClickBehavior = e.target.value);
+    handleOptionChange = (onClick: string) => {
+        this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick);
         SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => {
             docView.noOnClick();
-            switch (e.target.value) {
+            switch (onClick) {
                 case "enterPortal": docView.makeIntoPortal(); break;
                 case "toggleDetail": docView.setToggleDetail(); break;
                 case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break;
@@ -149,20 +150,34 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @computed
     get onClickFlyout() {
-        const makeLabel = (value: string, label: string) => <div className="radio">
-            <label>
-                <input type="radio" value={value} checked={(this.selectedDoc?.onClickBehavior ?? "nothing") === value} onChange={this.handleOptionChange} />
-                {label}
-            </label>
-        </div>;
+        const buttonList = [
+            ["nothing", "Select Document"],
+            ["enterPortal", "Enter Portal"],
+            ["toggleDetail", "Toggle Detail"],
+            ["linkInPlace", "Follow Link"],
+            ["linkOnRight", "Open Link on Right"]
+        ]
+        const currentSelection = this.selectedDoc.onClickBehavior;
+        // Get items to place into the list
+
+        const list = buttonList.map((value) => {
+            const click = () => {
+                this.handleOptionChange(value[0]);
+            };
+            return <div className="list-item" key={`${value}`}
+                style={{
+                    backgroundColor: value[0] === currentSelection ? Colors.LIGHT_BLUE : undefined
+                }}
+                onClick={click}>
+                {value[1]}
+            </div>;
+        });
         return <div>
-            <form>
-                {makeLabel("nothing", "Select Document")}
-                {makeLabel("enterPortal", "Enter Portal")}
-                {makeLabel("toggleDetail", "Toggle Detail")}
-                {makeLabel("linkInPlace", "Follow Link")}
-                {makeLabel("linkOnRight", "Open Link on Right")}
-            </form>
+            <div>
+                <div className="propertiesButton-dropdownList">
+                    {list}
+                </div>
+            </div>
             {Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
         </div>;
     }
@@ -190,7 +205,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform;
         const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree;
         const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>;
-
+        const isNovice = Doc.UserDoc().noviceMode;
         return !this.selectedDoc ? (null) :
             <div className="propertiesButtons">
                 {toggle(this.titleButton)}
@@ -198,7 +213,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.lockButton)}
                 {toggle(this.dictationButton)}
                 {toggle(this.onClickButton)}
-                {toggle(this.fitWidthButton)}
+                {toggle(this.fitWidthButton, { display: isNovice ? "none" : "" })}
                 {toggle(this.fitContentButton, { display: !isFreeForm ? "none" : "" })}
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })}
                 {toggle(this.maskButton, { display: !isInk ? "none" : "" })}
@@ -207,7 +222,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.snapButton, { display: isCollection ? "" : "none" })}
                 {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })}
                 {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })}
-                {toggle(this.perspectiveButton, { display: !isCollection ? "none" : "" })}
+                {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? "none" : "" })}
             </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 0e3663f65..01b79ffd2 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -106,9 +106,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 {user}
             </div>;
         };
+        // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
         return !this.props.showSidebar ? (null) :
             <div style={{
-                position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: 0, right: 0,
+                position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, 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`,
                 height: "100%"
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index fa35081bc..e528e84e3 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -89,7 +89,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     switch (property.split(":")[0]) {
         case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen);
         case StyleProp.DocContents: return undefined;
-        case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey";
+        case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
         case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"]);
@@ -108,7 +108,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.TitleHeight: return 15;
         case StyleProp.BorderPath: return comicStyle() && props?.renderDepth ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
         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.Masonry].includes(doc?._viewType as any) ||
+        case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
             doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
         case StyleProp.BackgroundColor: {
             if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6bdeaf722..6a22acae8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -338,7 +338,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {this.renderColors(this._col)}
             <div className="collectionSchema-headerMenu-group">
                 <button onClick={() => { this.deleteColumn(this._col.heading); }}
-                >Delete Column</button>
+                >Hide Column</button>
             </div>
         </div>;
     }
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 4b123c8b6..2f002736d 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -14,6 +14,30 @@
     width: 100%;
 }
 
+// TODO:glr Turn this into a seperate class
+.documentButtonMenu {
+    position: relative;
+    height: fit-content;
+    border-bottom: $standard-border;
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+    align-items: center;
+    align-content: center;
+    padding: 5px 0 5px 0;
+
+    .documentExplanation {
+        width: 90%;
+        margin: 5px;
+        font-size: 11px;
+        background-color: $light-blue;
+        color: $medium-blue;
+        padding: 10px;
+        border-radius: 10px;
+        border: solid 2px $medium-blue;
+    }
+}
+
 .collectionStackingView,
 .collectionMasonryView {
     height: 100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4fedda1bd..3c9084f64 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { CursorProperty } from "csstype";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
 import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
@@ -11,7 +11,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
 import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils, returnTrue, returnEmptyDoclist, returnEmptyFilter } from "../../../Utils";
 import { DocUtils, Docs } from "../../documents/Documents";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { SnappingManager } from "../../util/SnappingManager";
@@ -23,12 +23,14 @@ import { EditableView } from "../EditableView";
 import { LightboxView } from "../LightboxView";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
-import { StyleProp } from "../StyleProvider";
+import { StyleProp, DefaultStyleProvider } from "../StyleProvider";
 import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
 import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
+import { FontIconBox } from "../nodes/button/FontIconBox";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 const _global = (window /* browser */ || global /* node */) as any;
 
 type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -514,6 +516,47 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
     }
 
+    @computed get buttonMenu() {
+        const menuDoc:Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        // TODO:glr Allow support for multiple buttons
+        if (menuDoc){
+            const width: number = NumCast(menuDoc._width, 30);
+            const height: number = NumCast(menuDoc._height, 30);
+            console.log(menuDoc.title, width, height);
+            return (<div className="buttonMenu-docBtn"
+                style = {{width: width, height: height}}>
+                <DocumentView
+                    Document={menuDoc}
+                    DataDoc={menuDoc}
+                    isContentActive={this.props.isContentActive}
+                    isDocumentActive={returnTrue}
+                    addDocument={this.props.addDocument}
+                    moveDocument={this.props.moveDocument}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={emptyFunction}
+                    rootSelected={this.props.isSelected}
+                    removeDocument={this.props.removeDocument}
+                    ScreenToLocalTransform={Transform.Identity}
+                    PanelWidth={() => 35}
+                    PanelHeight={() => 35}
+                    renderDepth={this.props.renderDepth}
+                    focus={emptyFunction}
+                    styleProvider={this.props.styleProvider}
+                    layerProvider={this.props.layerProvider}
+                    docViewPath={returnEmptyDoclist}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    bringToFront={emptyFunction}
+                    docFilters={this.props.docFilters}
+                    docRangeFilters={this.props.docRangeFilters}
+                    searchFilterDocs={this.props.searchFilterDocs}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined} 
+                />
+            </div>
+            );
+        }
+    }
+
 
     @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
     @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }
@@ -529,34 +572,50 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             SetValue: this.addGroup,
             contents: "+ ADD A GROUP"
         };
+        const buttonMenu = this.rootDoc.buttonMenu;
+        const noviceExplainer = this.rootDoc.explainer;
+        console.log(noviceExplainer);
         return (
-            <div className="collectionStackingMasonry-cont" >
-                <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
-                    ref={this.createRef}
-                    style={{
-                        overflowY: this.props.isContentActive() ? "auto" : "hidden",
-                        background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
-                        pointerEvents: this.backgroundEvents ? "all" : undefined
-                    }}
-                    onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
-                    onDrop={this.onExternalDrop.bind(this)}
-                    onContextMenu={this.onContextMenu}
-                    onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
-                    {this.renderedSections}
-                    {!this.showAddAGroup ? (null) :
-                        <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
-                            style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
-                            <EditableView {...editableViewProps} />
-                        </div>}
-                    {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
-                        <Switch
-                            onChange={this.onToggle}
-                            onClick={this.onToggle}
-                            defaultChecked={true}
-                            checkedChildren="edit"
-                            unCheckedChildren="view"
-                        />} */}
-                </div> </div>
+            <>
+                {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
+                    {buttonMenu ? this.buttonMenu : null}
+                    {Doc.UserDoc().isNovice && noviceExplainer ? 
+                        <div className="documentExplanation">
+                            {noviceExplainer}
+                        </div>
+                        : null
+                    }
+                </div> : null}
+                <div className="collectionStackingMasonry-cont" >
+                    <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
+                        ref={this.createRef}
+                        style={{
+                            overflowY: this.props.isContentActive() ? "auto" : "hidden",
+                            background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+                            pointerEvents: this.backgroundEvents ? "all" : undefined
+                        }}
+                        onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
+                        onDrop={this.onExternalDrop.bind(this)}
+                        onContextMenu={this.onContextMenu}
+                        onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
+                        {this.renderedSections}
+                        {!this.showAddAGroup ? (null) :
+                            <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
+                                style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+                                <EditableView {...editableViewProps} />
+                            </div>}
+                        {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
+                            <Switch
+                                onChange={this.onToggle}
+                                onClick={this.onToggle}
+                                defaultChecked={true}
+                                checkedChildren="edit"
+                                unCheckedChildren="view"
+                            />} */}
+                    </div> 
+                </div>
+            </>
+            
         );
     }
 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 89d42439e..4d62a1af4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { Document, listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyFunction } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -26,6 +26,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { TreeView } from "./TreeView";
 import React = require("react");
+import { Transform } from '../../util/Transform';
 const _global = (window /* browser */ || global /* node */) as any;
 
 export type collectionTreeViewProps = {
@@ -221,8 +222,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     }
     childContextMenuItems = () => {
         const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
-        const filterScripts = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
-        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: filterScripts[i], label }));
+        const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
+        const icons = StrListCast(this.doc.childContextMenuIcons);
+        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     }
     @computed get treeViewElements() {
         TraceMobx();
@@ -265,13 +267,48 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         return hideTitle ? (null) : (this.outlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren);
     }
 
-    @computed get renderClearButton() {
-        return !this.doc.treeViewShowClearButton ? (null) : <div key="toolbar">
-            <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}>
-                <FontAwesomeIcon icon={"trash"} size="sm" />
-            </button>
-        </div >;
+    @computed get buttonMenu() {
+        const menuDoc:Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        // To create a multibutton menu add a CollectionLinearView
+        if (menuDoc){
+            
+            const width: number = NumCast(menuDoc._width, 30);
+            const height: number = NumCast(menuDoc._height, 30);
+            console.log(menuDoc.title, width, height);
+            return (<div className="buttonMenu-docBtn"
+                style = {{width: width, height: height}}>
+                <DocumentView
+                    Document={menuDoc}
+                    DataDoc={menuDoc}
+                    isContentActive={this.props.isContentActive}
+                    isDocumentActive={returnTrue}
+                    addDocument={this.props.addDocument}
+                    moveDocument={this.props.moveDocument}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={emptyFunction}
+                    rootSelected={this.props.isSelected}
+                    removeDocument={this.props.removeDocument}
+                    ScreenToLocalTransform={Transform.Identity}
+                    PanelWidth={() => 35}
+                    PanelHeight={() => 35}
+                    renderDepth={this.props.renderDepth + 1}
+                    focus={emptyFunction}
+                    styleProvider={this.props.styleProvider}
+                    layerProvider={this.props.layerProvider}
+                    docViewPath={returnEmptyDoclist}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    bringToFront={emptyFunction}
+                    docFilters={this.props.docFilters}
+                    docRangeFilters={this.props.docRangeFilters}
+                    searchFilterDocs={this.props.searchFilterDocs}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined} 
+                />
+            </div>);
+        }
     }
+
+    
     @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); }
     @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); }
 
@@ -295,22 +332,34 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         TraceMobx();
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined;
+        const buttonMenu = this.rootDoc.buttonMenu;
+        const noviceExplainer = this.rootDoc.explainer;
 
         return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
-            <div className="collectionTreeView-container"
-                style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
-                onContextMenu={this.onContextMenu}>
-                <div className="collectionTreeView-dropTarget"
-                    style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
-                    onWheel={e => e.stopPropagation()}
-                    onDrop={this.onTreeDrop}
-                    ref={this.createTreeDropTarget}>
+               <>
                     {this.titleBar}
-                    {this.renderClearButton}
-                    <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
-                        {this.treeViewElements}
-                    </ul>
-                </div >
-            </div>;
+                    <div className="collectionTreeView-container"
+                        style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
+                        onContextMenu={this.onContextMenu}>
+                        {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
+                            {buttonMenu ? this.buttonMenu : null}
+                            {Doc.UserDoc().noviceMode && noviceExplainer ? 
+                                <div className="documentExplanation">
+                                    {noviceExplainer}
+                                </div>
+                                : null
+                            }
+                        </div> : null}
+                        <div className="collectionTreeView-dropTarget"
+                            style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
+                            onWheel={e => e.stopPropagation()}
+                            onDrop={this.onTreeDrop}
+                            ref={this.createTreeDropTarget}>
+                            <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
+                                {this.treeViewElements}
+                            </ul>
+                        </div >
+                    </div>
+               </>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 889db1ef6..34cb6ec55 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -387,9 +387,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     background={this.miniMapColor}
                     document={this._document}
                     tabView={this.tabView} />
-                <Tooltip style={{ display: this.disableMinimap() ? "none" : undefined }} key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? "Open minimap" : "Close minimap"}</div>}>
+                <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? "Open minimap" : "Close minimap"}</div>}>
                     <div className="miniMap-hidden"
                         style={{
+                            display: this.disableMinimap() || this._document._viewType !== "freeform" ? "none" : undefined,
                             color: this._document.hideMinimap ? Colors.BLACK : Colors.WHITE,
                             backgroundColor: this._document.hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
                             boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b9487054b..8cf34ddcc 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -525,7 +525,8 @@ export class TreeView extends React.Component<TreeViewProps> {
     childContextMenuItems = () => {
         const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
         const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
-        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], label }));
+        const icons = StrListCast(this.doc.childContextMenuIcons);
+        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     }
     onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
 
@@ -835,7 +836,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         dontRegisterView: boolean | undefined,
         observerHeight: (ref: any) => void,
         unobserveHeight: (ref: any) => void,
-        contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string }[])
+        contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
     ) {
         const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
index f249eb1f5..8fe804466 100644
--- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss
@@ -98,7 +98,11 @@
             transition: transform 0.2s;
             align-items: center;
             justify-content: center;
-            transition: 0.15s;
+            transition: 0.2s;
+
+            &:hover{
+                filter: brightness(0.85);
+            }
         }
 
         >input {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index b28c32e0e..a25f962df 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -191,7 +191,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
                         {this.renderSorting()}
                         {this.renderColors()}
                         <div className="collectionSchema-headerMenu-group">
-                            <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Delete Column</button>
+                            <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
                         </div>
                     </>
                 }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index fed64b620..dfe99ffc8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -338,7 +338,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {this.renderColors(this._col)}
             <div className="collectionSchema-headerMenu-group">
                 <button onClick={() => { this.deleteColumn(this._col.heading); }}
-                >Delete Column</button>
+                >Hide Column</button>
             </div>
         </div>;
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index f5a7e61aa..8962d29f0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -638,12 +638,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
                                 </div>
                             </div>
                         ) : (
-                                <button
+                                <div
                                     className={`audiobox-record${interactive}`}
                                     style={{ backgroundColor: Colors.DARK_GRAY }}
                                 >
                                     RECORD
-                                </button>
+                                </div>
                             )}
                     </div>
                 ) : (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7463e192f..98ae279db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -635,7 +635,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     makeIntoPortal = async () => {
         const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
         if (!portalLink) {
-            const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ".portal" });
+            const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
         }
         this.Document.followLinkLocation = "inPlace";
@@ -679,7 +679,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription("UI Controls...");
             const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
             !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
-            appearanceItems.push({
+            !Doc.UserDoc().noviceMode && appearanceItems.push({
                 description: "Add a Field", event: () => {
                     const alias = Doc.MakeAlias(this.rootDoc);
                     alias.layout = FormattedTextBox.LayoutString("newfield");
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 72dec6e4c..f44355929 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,3 +1,5 @@
+@import "../global/globalCssVariables.scss";
+
 .pdfBox,
 .pdfBox-interactive {
     display: inline-block;
@@ -18,12 +20,13 @@
         top: 0;
         left: 0;
 
+        // glr: This should really be the same component as text and PDFs
         .pdfBox-sidebarBtn {
-            background: #121721;
+            background: $black;
             height: 25px;
             width: 25px;
-            right: 0;
-            color: white;
+            right: 5px;
+            color: $white;
             display: flex;
             position: absolute;
             align-items: center;
@@ -31,6 +34,13 @@
             border-radius: 3px;
             pointer-events: all;
             z-index: 1; // so it appears on top of the document's title, if shown
+
+            box-shadow: $standard-box-shadow;
+            transition: 0.2s;
+
+            &:hover{
+                filter: brightness(0.85);
+            }
         }
 
         .pdfBox-pageNums {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5e07229c1..3a399bfdb 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -24,6 +24,7 @@ import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
 import { AnchorMenu } from '../pdf/AnchorMenu';
+import { Colors } from '../global/globalEnums';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -208,11 +209,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         onClick={action(() => this._pageControls = !this._pageControls)} />
                     {this._pageControls ? pageBtns : (null)}
                 </div>
-                <button className="pdfBox-sidebarBtn" title="Toggle Sidebar"
-                    style={{ display: !this.props.isContentActive() ? "none" : undefined }}
+                <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={this.sidebarBtnDown} >
-                    <FontAwesomeIcon icon={"chevron-left"} size="sm" />
-                </button>
+                        <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+                </div>
             </div>;
     }
     sidebarWidth = () => !this.SidebarShown ? 0 :
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 19b69ff5a..417a17d96 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -10,7 +10,7 @@
         background: #121721;
         height: 25px;
         width: 25px;
-        right: 0;
+        right: 5px;
         display: flex;
         position: absolute;
         align-items: center;
@@ -18,6 +18,13 @@
         border-radius: 3px;
         pointer-events: all;
         z-index: 1; // so it appears on top of the document's title, if shown
+
+        box-shadow: $standard-box-shadow;
+        transition: 0.2s;
+
+        &:hover{
+            filter: brightness(0.85);
+        }
     }
 
     .pdfViewerDash-dragAnnotationBox {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 719e5a796..1f85219d6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -33,6 +33,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { LinkDocPreview } from "./LinkDocPreview";
 import "./WebBox.scss";
 import React = require("react");
+import { Colors } from "../global/globalEnums";
 const _global = (window /* browser */ || global /* node */) as any;
 const htmlToText = require("html-to-text");
 
@@ -590,11 +591,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
                 />
-                <button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
-                    style={{ display: !this.props.isContentActive() ? "none" : undefined }}
+                <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={this.sidebarBtnDown} >
-                    <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
-                </button>
+                        <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+                </div>
             </div>);
     }
 }
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index 48fb2c8dc..a2da35fe1 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -7,6 +7,7 @@
     align-items: center;
     font-size: 80%;
     border-radius: $standard-border-radius;
+    transition: 0.15s;
 
     .menuButton-wrap {
         grid-column: 1;
@@ -29,6 +30,9 @@
         font-family: 'ROBOTO';
         text-transform: uppercase;
         font-weight: bold;
+        transition: 0.15s;
+
+
     }
 
     .fontIconBox-icon {
@@ -50,7 +54,18 @@
     }
 
     &.textBtn {
+        display: grid;
+        /* grid-row: auto; */
+        grid-auto-flow: column;
+        cursor: pointer;
         width: 100%;
+        justify-content: center;
+        align-items: center;
+        justify-items: center;
+
+        &:hover {
+            filter:brightness(0.85) !important;
+        }
     }
 
     &.tglBtn {
@@ -127,13 +142,13 @@
         }
 
         &:hover {
-            background-color: rgba(0, 0, 0, 0.3) !important;
+            background-color: rgba(0, 0, 0, 0.3);
         }
     }
 
     &.toolBtn {
         cursor: pointer;
-        width: 40px;
+        width: 100%;
         border-radius: 100%;
 
         svg {
@@ -143,7 +158,7 @@
     }
 
     &.menuBtn {
-        cursor: pointer;
+        cursor: pointer !important;
         border-radius: 0px;
         flex-direction: column;
 
@@ -151,6 +166,10 @@
             width: 45% !important;
             height: 45%;
         }
+        
+        &:hover{
+            filter: brightness(0.85);
+        }
     }
 
 
@@ -163,8 +182,8 @@
 
         .colorButton-color {
             margin-top: 3px;
-            width: 90%;
-            height: 6px;
+            width: 80%;
+            height: 3px;
         }
 
         .menuButton-dropdownBox {
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index bccf733f0..1b27d562a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -253,11 +253,12 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             } else if (selected) {
                 dropdown = false;
                 text = StrCast(selected.Document.type);
+                if (text === DocumentType.RTF) text = "Text";
                 icon = Doc.toIcon(selected.Document);
             } else {
                 dropdown = false;
-                noneSelected = true;
-                text = "None selected";
+                icon = "globe-asia";
+                text = "User Default";
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
         } else if (script === 'setFont') {
@@ -302,7 +303,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             <div className={`menuButton ${this.type} ${active}`}
                 style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
                 onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
-                {dropdown || noneSelected ? (null) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+                {dropdown || noneSelected ? (null) : <FontAwesomeIcon style={{marginLeft: 5}} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
                 <div className="menuButton-dropdown-header">
                     {text && text[0].toUpperCase() + text.slice(1)}
                 </div>
@@ -487,7 +488,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             </div>;
 
         const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) :
-            <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
+            <div className="fontIconBox-label" style={{ color: color, backgroundColor: "transparent" }}>
                 {this.label}
             </div>;
 
@@ -508,7 +509,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         switch (this.type) {
             case ButtonType.TextButton:
                 button = (
-                    <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1 }}>
+                    <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
                         {buttonText ? 
                             <div className="button-text">
@@ -558,7 +559,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 break;
             case ButtonType.MenuButton:
                 const trailsIcon = <img src={`/assets/${"presTrails.png"}`}
-                    style={{ width: 20, height: 20, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
+                    style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}>
                         {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
@@ -605,7 +606,7 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo
 Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
     if (checkResult && selected) {
-        if (NumCast(selected.Document.z) === 1) return Colors.MEDIUM_BLUE;
+        if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
         else return "transparent";
     }
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 3cedab1a4..56f5c5631 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -66,14 +66,25 @@ audiotag:hover {
     
 .formattedTextBox-sidebar-handle {
     position: absolute;
-    top: 0;
+    top: 5px;
     //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
-    width: 10px;
+    width: 25px;
     height: 100%;
-    max-height: 35px;
-    background: lightgray;
-    border-radius: 20px;
+    max-height: 25px;
+    color: white;
+    background: $medium-gray;
+    border-radius: 5px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
     cursor:grabbing;
+    box-shadow: $standard-box-shadow;
+    // transition: 0.2s;
+
+    &:hover{
+        filter: brightness(0.85);
+    }
+
 }
 
 .formattedTextBox-sidebar,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f45b9de7a..468edf6cc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -63,6 +63,8 @@ import { SummaryView } from "./SummaryView";
 import applyDevTools = require("prosemirror-dev-tools");
 import React = require("react");
 import { SidebarAnnos } from '../../SidebarAnnos';
+import { Colors } from '../../global/globalEnums';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
@@ -574,11 +576,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         changeItems.push({ description: "plain", event: undoBatch(() => Doc.setNativeView(this.rootDoc)), icon: "eye" });
         const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
         DocListCast(noteTypesDoc?.data).forEach(note => {
+            const icon: IconProp = StrCast(note.icon) as IconProp;
             changeItems.push({
                 description: StrCast(note.title), event: undoBatch(() => {
                     Doc.setNativeView(this.rootDoc);
                     DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
-                }), icon: "eye"
+                }), icon: icon
             });
         });
         !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
@@ -1481,11 +1484,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @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, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
         return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
             style={{
-                left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
-                background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
-            }} />;
+                left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 30px"}))`,
+                backgroundColor: backgroundColor,
+                color: color
+            }} >
+                <FontAwesomeIcon icon={"comment-alt"}/>
+            </div>;
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 260ddfc90..e70b2bc19 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -114,7 +114,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
             const linkFrom = this.props.linkFrom();
             if (linkFrom) {
                 console.log(linkFrom.title);
-                DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
+                DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link");
             }
         }
     });
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 05aeb177c..923f1892e 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -33,12 +33,12 @@
             align-self: center;
             border-radius: $standard-border-radius;
             padding: 5px;
-            transition: linear 0.1s;
+            transition: linear 0.2s;
             color: $black;
             background-color: $light-gray;
 
             &:hover {
-                background-color: $light-blue;
+                background-color: darken($color: $light-gray, $amount: 20);
             }
         }
 
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 4dbd5e22c..d5254e315 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -51,7 +51,8 @@ export class TopBar extends React.Component {
                         </div>
                     </div>
                     <div className="topbar-right" >
-                        <div className="topbar-icon">
+                        <div className="topbar-icon" onClick={() => window.open(
+                            "https://brown-dash.github.io/Dash-Documentation/", "_blank")}>
                             {"Help"}<FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
                         </div>
                         <div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>
-- 
cgit v1.2.3-70-g09d2


From 5c6946fd211f6b0ee5d860ade968f4353344974d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Sep 2021 12:14:20 -0400
Subject: fixed autoHeight for formattedText to work better with stacking and
 contentfitting views.

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

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 0bcdbac19..9fe2122e6 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -218,8 +218,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })}
                 {toggle(this.maskButton, { display: !isInk ? "none" : "" })}
                 {toggle(this.chromeButton, { display: !isCollection || isNovice ? "none" : "" })}
-                {toggle(this.gridButton, { display: isCollection ? "" : "none" })}
-                {toggle(this.snapButton, { display: isCollection ? "" : "none" })}
+                {toggle(this.gridButton, { display: !isCollection ? "none" : "" })}
+                {toggle(this.snapButton, { display: !isCollection ? "none" : "" })}
                 {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })}
                 {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })}
                 {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? "none" : "" })}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 468edf6cc..59d43f8d7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -905,7 +905,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }, { fireImmediately: true }
         );
         quickScroll = undefined;
-        this.tryUpdateScrollHeight();
+        setTimeout(this.tryUpdateScrollHeight, 10);
     }
 
     pushToGoogleDoc = async () => {
@@ -1492,8 +1492,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 backgroundColor: backgroundColor,
                 color: color
             }} >
-                <FontAwesomeIcon icon={"comment-alt"}/>
-            </div>;
+            <FontAwesomeIcon icon={"comment-alt"} />
+        </div>;
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
-- 
cgit v1.2.3-70-g09d2


From 80baae4dcf49f818dca4dbfe340013e0e9d197bb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Sep 2021 03:58:21 -0400
Subject: fixed drawing of link lines - doesn't generate duplicates, and works
 with text regions, scroll locations, and document anchors.

---
 src/client/documents/Documents.ts                  |  9 +++---
 src/client/util/DocumentManager.ts                 | 37 ++++++++++++++--------
 .../CollectionFreeFormLinkView.tsx                 |  8 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 24 ++++++++++----
 src/client/views/nodes/PDFBox.tsx                  | 10 +++---
 src/client/views/nodes/WebBox.tsx                  | 10 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 8 files changed, 63 insertions(+), 39 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 bd247bd01..671e27408 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -157,6 +157,7 @@ export class DocumentOptions {
     z?: number; // whether document is in overlay (1) or not (0 or undefined)
     author?: string;
     _layoutKey?: string;
+    unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf)
     type?: string;
     title?: string;
     "acl-Public"?: string; // public permissions
@@ -277,7 +278,7 @@ export class DocumentOptions {
     // Action Button
     buttonMenu?: boolean; // whether a action button should be displayed 
     buttonMenuDoc?: Doc;
-    explainer?:string;
+    explainer?: string;
 
     treeViewOpenIsTransient?: boolean; // ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view
     _treeViewOpen?: boolean; // whether this document is expanded in a tree view  (note: need _ and regular versions since this can be specified for both proto and layout docs)
@@ -1188,7 +1189,7 @@ export namespace DocUtils {
             })) as ContextMenuProps[],
             icon: "sticky-note"
         });
-        const math:ContextMenuProps = ({
+        const math: ContextMenuProps = ({
             description: ":Math", event: () => {
                 const created = Docs.Create.EquationDocument();
                 if (created) {
@@ -1202,7 +1203,7 @@ export namespace DocUtils {
                 }
             }, icon: "calculator"
         });
-        const documentList:ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
+        const documentList: ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
             description: ":" + StrCast(dragDoc.title),
             event: undoBatch((args: { x: number, y: number }) => {
                 const newDoc = Doc.copyDragFactory(dragDoc);
@@ -1215,7 +1216,7 @@ export namespace DocUtils {
                 }
             }),
             icon: Doc.toIcon(dragDoc),
-        })) as ContextMenuProps[]; 
+        })) as ContextMenuProps[];
         documentList.push(math)
         ContextMenu.Instance.addItem({
             description: "Create document",
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index cb0ee411c..b72a3189d 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -25,24 +25,35 @@ export class DocumentManager {
 
     @action
     public AddView = (view: DocumentView) => {
-        DocListCast(view.rootDoc.links).forEach(link => {
-            const whichOtherAnchor = view.props.LayoutTemplateString?.includes("anchor2") ? "anchor1" : "anchor2";
-            const otherDoc = link && (link[whichOtherAnchor] as Doc);
-            const otherDocAnno = DocumentType.MARKER === otherDoc?.type ? otherDoc.annotationOn as Doc : undefined;
-            otherDoc && DocumentManager.Instance.DocumentViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, otherDoc) || Doc.AreProtosEqual(dv.rootDoc, otherDocAnno)).
-                forEach(otherView => {
-                    if (otherView.rootDoc.type !== DocumentType.LINK || otherView.props.LayoutTemplateString !== view.props.LayoutTemplateString) {
-                        this.LinkedDocumentViews.push({ a: whichOtherAnchor === "anchor1" ? otherView : view, b: whichOtherAnchor === "anchor1" ? view : otherView, l: link });
-                    }
-                });
-        });
+        //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
+        if (view.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
+            const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1";
+            view.props.LayoutTemplateString && DocListCast(view.rootDoc.links).forEach(link => {
+                DocumentManager.Instance.DocumentViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).
+                    forEach(otherView => {
+                        this.LinkedDocumentViews.push(
+                            {
+                                a: viewAnchorIndex === "anchor2" ? otherView : view,
+                                b: viewAnchorIndex === "anchor2" ? view : otherView,
+                                l: link
+                            });
+                    });
+            });
+            // this.LinkedDocumentViews.forEach(view => console.log("  LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
+            //     view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
+        }
         this.DocumentViews.push(view);
     }
     public RemoveView = action((view: DocumentView) => {
+        this.LinkedDocumentViews.slice().forEach(action(pair => {
+            if (pair.a === view || pair.b === view) {
+                const li = this.LinkedDocumentViews.indexOf(pair);
+                li !== -1 && this.LinkedDocumentViews.splice(li, 1);
+            }
+        }));
+
         const index = this.DocumentViews.indexOf(view);
         index !== -1 && this.DocumentViews.splice(index, 1);
-
-        this.LinkedDocumentViews.slice().forEach(action((pair, i) => pair.a === view || pair.b === view ? this.LinkedDocumentViews.splice(i, 1) : null));
     });
 
     //gets all views
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 16258404d..6fe46bbb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -43,8 +43,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
         setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
         setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
-        const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
-        const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+        const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+        const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
         const adiv = acont.length ? acont[0] : A.ContentDiv;
         const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
         const a = adiv.getBoundingClientRect();
@@ -140,8 +140,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
         const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
         const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
-        const adiv = (acont.length ? acont[0] : A.ContentDiv);
-        const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+        const adiv = acont.length ? acont[0] : A.ContentDiv;
+        const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
         for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
         for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
         const a = adiv.getBoundingClientRect();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dd1f3e707..2deaea823 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1052,7 +1052,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             freezeDimensions={this.props.childFreezeDimensions}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             bringToFront={this.bringToFront}
-            dontRegisterView={this.props.dontRegisterView}
+            dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
             pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
                 (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
             jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b2629200d..2c9daa370 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -847,16 +847,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             default: return this.props.styleProvider?.(doc, props, property);
         }
     }
-    @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
+    // We need to use allrelatedLinks to get not just links to the document as a whole, but links to 
+    // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document).  e.g., 
+    //     - PDF text regions are rendered as an Annotations without generating a DocumentView, '
+    //     - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
+    //     - and links to PDF/Web docs at a certain scroll location never create an explicit view.
+    // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+    @computed get directLinks() {
+        TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
+            Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
+            Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
+            ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
+            ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
+        );
+    }
     @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
     @computed get allLinkEndpoints() {  // the small blue dots that mark the endpoints of links
         TraceMobx();
-        if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
+        if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
         if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
-        // need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews.  All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
-        const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
+        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
         return filtered.map((link, i) =>
-            <div className="documentView-anchorCont" key={i + 1}>
+            <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView {...this.props}
                     Document={link}
                     PanelWidth={this.anchorPanelWidth}
@@ -1154,7 +1166,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        !this.props.dontRegisterView && DocumentManager.Instance.RemoveView(this);
+        !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3a399bfdb..2bafb3b7f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -79,9 +79,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const anchor =
             AnchorMenu.Instance?.GetAnchor() ??
             Docs.Create.TextanchorDocument({
-                title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
-                annotationOn: this.rootDoc,
+                title: StrCast(this.rootDoc.title + "@" + this.layoutDoc._scrollTop?.toFixed(0)),
                 y: NumCast(this.layoutDoc._scrollTop),
+                unrendered: true
             });
         this.addDocument(anchor);
         return anchor;
@@ -210,13 +210,13 @@ 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, 
+                    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" />
+                    <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
                 </div>
             </div>;
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1f85219d6..eaedd48c7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -187,8 +187,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
             Docs.Create.WebanchorDocument(this._url, {
                 title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
-                annotationOn: this.rootDoc,
-                y: NumCast(this.layoutDoc._scrollTop)
+                y: NumCast(this.layoutDoc._scrollTop),
+                unrendered: true
             });
         this.addDocumentWrapper(anchor);
         return anchor;
@@ -592,13 +592,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     removeDocument={this.removeDocument}
                 />
                 <div className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
-                    style={{ 
-                        display: !this.props.isContentActive() ? "none" : undefined, 
+                    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" />
+                    <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
                 </div>
             </div>);
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 59d43f8d7..bc578f95d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -713,7 +713,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
             if (sel.from !== sel.to) {
-                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
+                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
                 const href = targetHref ?? Doc.localServerPath(anchor);
                 if (anchor !== anchorDoc) this.addDocument(anchor);
                 tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
-- 
cgit v1.2.3-70-g09d2


From 64ff849d21273f7440917ef15a4b1c7f1909687c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Sep 2021 13:16:48 -0400
Subject: fixed warnings.  cleaned up link lines a bit more - simplified, and
 allowed multiple aliases to share a link.  fixed link lines to text anchors
 from flapping around wildly when camera pans

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 12 +++----
 src/client/util/DocumentManager.ts                 | 38 +++++++++++++---------
 src/client/views/PropertiesButtons.tsx             |  2 +-
 .../CollectionFreeFormLinkView.tsx                 | 13 +++++---
 .../CollectionFreeFormLinksView.tsx                | 19 ++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/nodes/PDFBox.tsx                  | 10 +++---
 src/client/views/nodes/WebBox.tsx                  |  4 +--
 .../views/nodes/button/textButton/TextButton.tsx   |  2 +-
 src/client/views/nodes/button/textButton/index.ts  |  2 +-
 .../views/nodes/button/toggleButton/index.ts       |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 13 +++-----
 src/client/views/pdf/PDFViewer.tsx                 |  5 ++-
 15 files changed, 60 insertions(+), 67 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 671e27408..9a45da440 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1217,7 +1217,7 @@ export namespace DocUtils {
             }),
             icon: Doc.toIcon(dragDoc),
         })) as ContextMenuProps[];
-        documentList.push(math)
+        documentList.push(math);
         ContextMenu.Instance.addItem({
             description: "Create document",
             subitems: documentList,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 56807c63b..f9c55da29 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -832,7 +832,7 @@ export class CurrentUserUtils {
         await doc.myTrails;
         if (doc.myTrails === undefined) {
             const newTrail = ScriptField.MakeScript(`createNewPresentation()`);
-            const newTrailButton:Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "New trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
+            const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "New trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
             doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
                 title: "My Trails", _showTitle: "title", _height: 100,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
@@ -852,16 +852,16 @@ export class CurrentUserUtils {
             doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
             // doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true });
             const newFolder = ScriptField.MakeFunction(`doc.makeFolder()`, { doc: doc.myFilesystem })!;
-            const newFolderButton:Doc = Docs.Create.FontIconDocument({ onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true });
+            const newFolderButton: Doc = Docs.Create.FontIconDocument({ onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true });
             doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], {
-                title: "My Documents", _showTitle: "title",  buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
+                title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
                 treeViewTruncateTitleWidth: 150, ignoreClick: true,
                 isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
                 explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
             }));
-            (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder!]);
+            (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder]);
             (doc.myTrails as any as Doc).contextMenuLabels = new List<string>(["Create new folder"]);
         }
         return doc.myFilesystem as any as Doc;
@@ -870,7 +870,7 @@ export class CurrentUserUtils {
     static setupRecentlyClosedDocs(doc: Doc) {
         if (doc.myRecentlyClosedDocs === undefined) {
             const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
-            const clearDocsButton:Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
+            const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
             doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
                 title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
@@ -1215,7 +1215,7 @@ export class CurrentUserUtils {
     // Import sidebar is where shared documents are contained
     static setupUploadSidebar(doc: Doc) {
         if (doc.myUploadDocs === undefined) {
-            const newUploadButton:Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Upload from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.ClickButton, buttonText: "Upload", icon: "upload", system: true });
+            const newUploadButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Upload from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.ClickButton, buttonText: "Upload", icon: "upload", system: true });
             doc.myUploadDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
                 title: "My Uploads", _forceActive: true, buttonMenu: true, buttonMenuDoc: newUploadButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
                 childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index b72a3189d..9e190ad02 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,19 +1,21 @@
 import { action, observable, runInAction } from 'mobx';
 import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { Cast } from '../../fields/Types';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
+import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { Scripting } from './Scripting';
 
 export class DocumentManager {
 
     //global holds all of the nodes (regardless of which collection they're in)
     @observable public DocumentViews: DocumentView[] = [];
+    @observable public LinkAnchorBoxViews: DocumentView[] = [];
     @observable public RecordingEvent = 0;
     @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
 
@@ -26,23 +28,24 @@ export class DocumentManager {
     @action
     public AddView = (view: DocumentView) => {
         //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
-        if (view.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
+        if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1";
-            view.props.LayoutTemplateString && DocListCast(view.rootDoc.links).forEach(link => {
-                DocumentManager.Instance.DocumentViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).
-                    forEach(otherView => {
-                        this.LinkedDocumentViews.push(
-                            {
-                                a: viewAnchorIndex === "anchor2" ? otherView : view,
-                                b: viewAnchorIndex === "anchor2" ? view : otherView,
-                                l: link
-                            });
-                    });
+            DocListCast(view.rootDoc.links).forEach(link => {
+                this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).
+                    forEach(otherView => this.LinkedDocumentViews.push(
+                        {
+                            a: viewAnchorIndex === "anchor2" ? otherView : view,
+                            b: viewAnchorIndex === "anchor2" ? view : otherView,
+                            l: link
+                        })
+                    );
             });
+            this.LinkAnchorBoxViews.push(view);
             // this.LinkedDocumentViews.forEach(view => console.log("  LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
             //     view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
+        } else {
+            this.DocumentViews.push(view);
         }
-        this.DocumentViews.push(view);
     }
     public RemoveView = action((view: DocumentView) => {
         this.LinkedDocumentViews.slice().forEach(action(pair => {
@@ -52,8 +55,13 @@ export class DocumentManager {
             }
         }));
 
-        const index = this.DocumentViews.indexOf(view);
-        index !== -1 && this.DocumentViews.splice(index, 1);
+        if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const index = this.LinkAnchorBoxViews.indexOf(view);
+            this.LinkAnchorBoxViews.splice(index, 1);
+        } else {
+            const index = this.DocumentViews.indexOf(view);
+            index !== -1 && this.DocumentViews.splice(index, 1);
+        }
     });
 
     //gets all views
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 9fe2122e6..dd737dc9d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -156,7 +156,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             ["toggleDetail", "Toggle Detail"],
             ["linkInPlace", "Follow Link"],
             ["linkOnRight", "Open Link on Right"]
-        ]
+        ];
         const currentSelection = this.selectedDoc.onClickBehavior;
         // Get items to place into the list
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6fe46bbb2..3b3e069d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -5,7 +5,6 @@ import { Id } from "../../../../fields/FieldSymbols";
 import { List } from "../../../../fields/List";
 import { NumCast, StrCast } from "../../../../fields/Types";
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { DocumentType } from "../../../documents/DocumentTypes";
 import { LinkManager } from "../../../util/LinkManager";
 import { SnappingManager } from "../../../util/SnappingManager";
 import { DocumentView } from "../../nodes/DocumentView";
@@ -69,8 +68,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         } else {
             const m = targetAhyperlink.getBoundingClientRect();
             const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-            linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
-            linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+            const mpx = mp[0] / A.props.PanelWidth();
+            const mpy = mp[1] / A.props.PanelHeight();
+            if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
+            if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
         }
         if (!targetBhyperlink) {
             if (linkDoc.linkAutoMove) {
@@ -80,8 +81,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         } else {
             const m = targetBhyperlink.getBoundingClientRect();
             const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-            linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
-            linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+            const mpx = mp[0] / B.props.PanelWidth();
+            const mpy = mp[1] / B.props.PanelHeight();
+            if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
+            if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index e812064b7..dacbb3508 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,30 +1,17 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
-import { Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
-import { DocumentView } from "../../nodes/DocumentView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
     @computed get uniqueConnections() {
-        const connections = DocumentManager.Instance.LinkedDocumentViews
-            .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK)
-            .reduce((drawnPairs, connection) => {
-                const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b);
-                matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l));
-                if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) });
-                return drawnPairs;
-            }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]);
-        const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>();
-        connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) }));
-        return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />);
+        return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c =>
+            <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />
+        );
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2deaea823..018c2a895 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1457,7 +1457,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             getTransform={this.getTransform}
             isAnnotationOverlay={this.isAnnotationOverlay}>
             <div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
-                {this.layoutDoc["_backgroundGridShow"] ? this.backgroundGrid : (null)}
+                {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
                 <CollectionFreeFormViewPannableContents
                     isAnnotationOverlay={this.isAnnotationOverlay}
                     transform={this.contentTransform}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8f9959693..1e0172d24 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -3,7 +3,6 @@ import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from "../../../fields/FieldSymbols";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 2bafb3b7f..9706d73c7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,13 +3,13 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from '../../../fields/documentSchemas';
 import { makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
+import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch } from '../../util/UndoManager';
@@ -17,14 +17,14 @@ import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeF
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import { Colors } from '../global/globalEnums';
+import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PDFViewer } from "../pdf/PDFViewer";
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
-import { AnchorMenu } from '../pdf/AnchorMenu';
-import { Colors } from '../global/globalEnums';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index eaedd48c7..7e46d8433 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
@@ -24,6 +24,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
+import { Colors } from "../global/globalEnums";
 import { LightboxView } from "../LightboxView";
 import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { AnchorMenu } from "../pdf/AnchorMenu";
@@ -33,7 +34,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { LinkDocPreview } from "./LinkDocPreview";
 import "./WebBox.scss";
 import React = require("react");
-import { Colors } from "../global/globalEnums";
 const _global = (window /* browser */ || global /* node */) as any;
 const htmlToText = require("html-to-text");
 
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
index 414b50dcb..e18590a95 100644
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -12,6 +12,6 @@ export class TextButton extends Component<IButtonProps> {
         return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
             <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
             {this.props.label}
-        </div>)
+        </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts
index 01fcde54f..01d62eb7e 100644
--- a/src/client/views/nodes/button/textButton/index.ts
+++ b/src/client/views/nodes/button/textButton/index.ts
@@ -1 +1 @@
-export * from './TextButton'
\ No newline at end of file
+export * from './TextButton';
\ No newline at end of file
diff --git a/src/client/views/nodes/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts
index 48b28fb4c..cdb9c527c 100644
--- a/src/client/views/nodes/button/toggleButton/index.ts
+++ b/src/client/views/nodes/button/toggleButton/index.ts
@@ -1 +1 @@
-export * from './ToggleButton'
\ No newline at end of file
+export * from './ToggleButton';
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bc578f95d..cad9af2b0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1448,18 +1448,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             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) {
-                var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+                const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
                 if (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) {
                         setScrollHeight();
-                        // setTimeout(() => {
-                        //     proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                        //     scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-                        //     scrollHeight && setScrollHeight();
-                        // }, 10);
-                    } 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...
+                    } 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...
+                    }
                 }
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 02010e123..d953c6b6c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,14 +2,13 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
-import { createSchema } from "../../../fields/Schema";
 import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse, returnEmptyString, returnEmptyFilter } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-- 
cgit v1.2.3-70-g09d2


From a11cbed94999a437d3b51dbf8632441acdb8b5be Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Sep 2021 09:33:16 -0400
Subject: tried adjusting text annotation button so text would be more readable
 in corner.

---
 .../views/nodes/formattedText/FormattedTextBox.scss     | 17 +++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx      |  6 ++++--
 2 files changed, 11 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 56f5c5631..4134e3c67 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -66,11 +66,12 @@ audiotag:hover {
     
 .formattedTextBox-sidebar-handle {
     position: absolute;
-    top: 5px;
+    top: 0;
+    left: 17px;
     //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
-    width: 25px;
-    height: 100%;
-    max-height: 25px;
+    width: 17px;
+    height: 17px;
+    border-radius: 3px;
     color: white;
     background: $medium-gray;
     border-radius: 5px;
@@ -80,8 +81,9 @@ audiotag:hover {
     cursor:grabbing;
     box-shadow: $standard-box-shadow;
     // transition: 0.2s;
-
+    opacity: 0.3;
     &:hover{
+        opacity: 1 !important;
         filter: brightness(0.85);
     }
 
@@ -425,12 +427,7 @@ footnote::after {
     
     .formattedTextBox-sidebar-handle {
         position: absolute;
-        top: 0;
-        //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
-        width: 10px;
-        height: 35px;
         background: lightgray;
-        border-radius: 20px;
         cursor: grabbing;
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cad9af2b0..bc19320e9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1360,6 +1360,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._undoTyping = undefined;
         return wasUndoing;
     }
+
     @action
     onBlur = (e: any) => {
         FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
@@ -1485,9 +1486,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
         return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
             style={{
-                left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 30px"}))`,
+                left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 17px"}))`,
                 backgroundColor: backgroundColor,
-                color: color
+                color: color,
+                opacity: annotated ? 1 : undefined
             }} >
             <FontAwesomeIcon icon={"comment-alt"} />
         </div>;
-- 
cgit v1.2.3-70-g09d2


From 058cf10e63fe784ba97f288e24fc58d41e0d46f3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Sep 2021 10:56:05 -0400
Subject: made setting background undoable.  added setting header
 color/visibility from main toolbar.  made changing header contents possible
 using #<key>(;<key)* syntax

---
 src/client/util/CurrentUserUtils.ts                |  4 ++++
 src/client/views/nodes/DocumentView.tsx            | 18 +++++++++++++---
 src/client/views/nodes/button/FontIconBox.tsx      | 24 ++++++++++++++--------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 19 +++++++++--------
 4 files changed, 45 insertions(+), 20 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 f9c55da29..eb9184e88 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1049,6 +1049,10 @@ export class CurrentUserUtils {
                 title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
                 script: "setBackgroundColor", hidden: 'selectedDocumentType()'
             }, // Only when a document is selected
+            {
+                title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading",
+                script: "setHeaderColor", hidden: 'selectedDocumentType()'
+            }, // Only when a document is selected
             { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', checkResult: 'toggleOverlay(true)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
             // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected 
             { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2c9daa370..384d2e456 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -952,6 +952,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     onClick={this.onClickFunc}
                 />
             </div>;
+        const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
         const titleView = !showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
                 position: this.headerMargin ? "relative" : "absolute",
@@ -960,11 +961,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
-                    contents={showTitle === "title" ? StrCast((this.dataDoc || this.props.Document).title) : showTitle.split(";").map(field => field + ":" + (this.dataDoc || this.props.Document)[field]?.toString()).join(" ")}
+                    contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
                     display={"block"}
                     fontSize={10}
-                    GetValue={() => Field.toString((this.dataDoc || this.props.Document)[showTitle.split(";")[0]] as any as Field)}
-                    SetValue={undoBatch((value) => showTitle.includes("Date") ? true : (Doc.GetProto(this.dataDoc || this.props.Document)[showTitle] = value) ? true : true)}
+                    GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
+                    SetValue={undoBatch(value => {
+                        if (value?.startsWith(showTitle + "=")) {
+                            value = value.substring((showTitle + "=").length);
+                            if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
+                            if (showTitle.includes("Date") || showTitle === "author") return true;
+                            return Doc.SetInPlace(targetDoc, showTitle, value, true) ? true : true;
+                        } else if (value?.startsWith("#")) {
+                            Doc.UserDoc().showTitle = value?.substring(1) ? value.substring(1) : "creationDate";
+                            return true;
+                        }
+                        return true;
+                    })}
                 />
             </div>;
         return this.props.hideTitle || (!showTitle && !showCaption) ?
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index b72f31ef8..4475cf347 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -15,7 +15,7 @@ import { DocumentType } from '../../../documents/DocumentTypes';
 import { Scripting } from "../../../util/Scripting";
 import { SelectionManager } from '../../../util/SelectionManager';
 import { ColorScheme } from '../../../util/SettingsManager';
-import { UndoManager } from '../../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { CollectionViewType } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
 import { DocComponent } from '../../DocComponent';
@@ -31,6 +31,7 @@ import { ToggleButton } from './toggleButton';
 import { IButtonProps } from './ButtonInterface';
 import { FontIconBadge } from './FontIconBadge';
 import './FontIconBox.scss';
+import { undo } from 'prosemirror-history';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -303,7 +304,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             <div className={`menuButton ${this.type} ${active}`}
                 style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
                 onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
-                {dropdown || noneSelected ? (null) : <FontAwesomeIcon style={{marginLeft: 5}} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+                {dropdown || noneSelected ? (null) : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
                 <div className="menuButton-dropdown-header">
                     {text && text[0].toUpperCase() + text.slice(1)}
                 </div>
@@ -348,7 +349,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
         const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
             '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
-            '#FFFFFF', '#f1efeb'];
+            '#FFFFFF', '#f1efeb', "transparent"];
 
         const colorBox = (func: (color: ColorState) => void) => <SketchPicker
             disableAlpha={!stroke}
@@ -369,7 +370,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             const hex: string = value.hex;
             const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
             if (s) {
-                s.script.run().result;
+                undoBatch(() => s.script.run().result)();
             }
         };
         return (
@@ -493,7 +494,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 {this.label}
             </div>;
 
-        const buttonProps:IButtonProps = {
+        const buttonProps: IButtonProps = {
             type: this.type,
             rootDoc: this.rootDoc,
             label: label,
@@ -512,9 +513,9 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                        {buttonText ? 
+                        {buttonText ?
                             <div className="button-text">
-                                    {buttonText}
+                                {buttonText}
                             </div>
                             : null}
                         {label}
@@ -598,11 +599,18 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo
             return "#FFFFFF";
         }
     }
-    if (selected && selected.type === DocumentType.INK) selected.fillColor = color;
+    if (selected?.type === DocumentType.INK) selected.fillColor = color;
     if (selected) selected._backgroundColor = color;
     Doc.UserDoc()._fontColor = color;
 });
 
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setHeaderColor(color?: string, checkResult?: boolean) {
+    Doc.SharingDoc().userColor = undefined;
+    Doc.GetProto(Doc.SharingDoc()).userColor = color;
+    Doc.UserDoc().showTitle = color === "transparent" ? undefined : StrCast(Doc.UserDoc().showTitle, "creationDate");
+});
+
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bc19320e9..fbb291858 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1484,15 +1484,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         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, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
-        return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
-            style={{
-                left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 17px"}))`,
-                backgroundColor: backgroundColor,
-                color: color,
-                opacity: annotated ? 1 : undefined
-            }} >
-            <FontAwesomeIcon icon={"comment-alt"} />
-        </div>;
+        return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
+            <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+                style={{
+                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 17px"}))`,
+                    backgroundColor: backgroundColor,
+                    color: color,
+                    opacity: annotated ? 1 : undefined
+                }} >
+                <FontAwesomeIcon icon={"comment-alt"} />
+            </div>;
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
-- 
cgit v1.2.3-70-g09d2


From afbbb76afc5a9b1370374b337af1a03a2e94b5d7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Sep 2021 14:46:06 -0400
Subject: fixed anchor menu highlighter button's dropdown location.  fixed
 metadata view for formattedText.  added menu item for metadata view.  fixed
 formattedTextbox initialization.

---
 src/client/util/CurrentUserUtils.ts                | 15 ++++++-----
 src/client/util/Scripting.ts                       |  8 +++---
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  3 ++-
 src/client/views/nodes/DocumentView.tsx            | 20 +++++++-------
 .../views/nodes/formattedText/DashFieldView.tsx    |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 31 +++++++++++++++-------
 .../views/nodes/formattedText/RichTextMenu.scss    |  1 +
 src/client/views/pdf/AnchorMenu.scss               | 29 ++++++++++++++++++++
 src/client/views/pdf/AnchorMenu.tsx                | 13 +++++----
 10 files changed, 86 insertions(+), 38 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 eb9184e88..816503b37 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -22,6 +22,7 @@ import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMu
 import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
+import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { LabelBox } from "../views/nodes/LabelBox";
 import { OverlayView } from "../views/OverlayView";
@@ -37,8 +38,6 @@ import { ColorScheme } from "./SettingsManager";
 import { SharingManager } from "./SharingManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
-import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
-import { IconName } from "@fortawesome/fontawesome-svg-core";
 
 interface Button {
     title?: string;
@@ -447,16 +446,16 @@ export class CurrentUserUtils {
                 storedMarks: []
             };
             const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), {
-                title: "text", version: headerViewVersion, target: doc, _height: 70, _headerPointerEvents: "all",
+                title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all",
                 _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true,
                 cloneFieldFilter: new List<string>(["system"])
             }, "header");
             const headerBtnHgt = 10;
             headerTemplate[DataSym].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}px)'/>` +
-                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'}  dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize}px' height='{(this._headerHeight||1)}px' background='{this._headerColor ||this.target.mySharedDocs.userColor||`lightGray`}' />" +
-                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' background='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(1,this._height-30),this._headerHeight===1?50:1)) && (this._autoHeightMargins=this._headerHeight+${headerBtnHgt})’}  >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' 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+${headerBtnHgt})’}  >Metadata</HTMLdiv>` +
                 "</HTMLdiv>";
 
             // "<div style={'height:100%'}>" +
@@ -562,7 +561,7 @@ export class CurrentUserUtils {
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
                 title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
-                _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true,
+                _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
             }));
         } else {
@@ -1554,6 +1553,8 @@ Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
         view && SelectionManager.SelectView(view, false);
     }
 });
+Scripting.addGlobal(function MySharedDocs() { return Doc.SharingDoc(); },
+    "document containing all shared Docs");
 Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; },
     "is Dash in novice mode");
 Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index efee37419..40b94024e 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -171,7 +171,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
             if (!options.editable) {
                 batch = Doc.MakeReadOnly();
             }
-            
+
             const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
             if (batch) {
                 batch.end();
@@ -316,9 +316,9 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
     }
     const paramString = paramList.join(", ");
-    const funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
-        ${addReturn ? `return ${script};` : `return ${script};`}
-    })`;
+    const body = addReturn ? `return ${script};` : `return ${script};`;
+    const reqTypes = requiredType ? `: ${requiredType}` : '';
+    const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
     host.writeFile("file.ts", funcScript);
 
     if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 33dff9da5..cb36f4270 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -115,7 +115,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             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 as string || "";
+                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];
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 544125ede..fad905d6d 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -201,7 +201,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
             if (splits.length > 1) {
                 const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
                 layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
-                return ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name, scale: "number", value: "string" });
+                const script = code[1].value.replace(/^‘/, "").replace(/’$/, "");  // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
+                return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: "number", value: "string" });
             }
             return undefined;
             // add input function to props
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6eeb51ffe..6f97cdbd8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -712,7 +712,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
 
                 onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-                onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
+                !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
                 onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
 
                 if (!this.Document.annotationOn) {
@@ -970,19 +970,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     display={"block"}
                     fontSize={10}
                     GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
-                    SetValue={undoBatch(value => {
-                        if (value?.startsWith(showTitle + "=")) {
-                            value = value.substring((showTitle + "=").length);
-                            if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
-                            if (showTitle.includes("Date") || showTitle === "author") return true;
-                            return Doc.SetInPlace(targetDoc, showTitle, value, true) ? true : true;
-                        } else if (value?.startsWith("#")) {
+                    SetValue={undoBatch(input => {
+                        if (input?.startsWith("#")) {
                             if (this.props.showTitle) {
-                                this.rootDoc._showTitle = value?.substring(1) ? value.substring(1) : undefined;
+                                this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
                             } else {
-                                Doc.UserDoc().showTitle = value?.substring(1) ? value.substring(1) : "creationDate";
+                                Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
                             }
                             return true;
+                        } else {
+                            var value = input.replace(new RegExp(showTitle + "="), "");
+                            if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
+                            if (showTitle.includes("Date") || showTitle === "author") return true;
+                            return Doc.SetInPlace(targetDoc, showTitle, value, true) ? true : true;
                         }
                         return true;
                     })}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 62f65cdae..34908e54b 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -82,7 +82,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
         if (this._dashDoc) {
-            const dashVal = this._dashDoc[DataSym][this._fieldKey] ?? this._dashDoc[this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
+            const dashVal = this._dashDoc[this._fieldKey] ?? this._dashDoc[DataSym][this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
             const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
             const boolVal = Cast(fval, "boolean", null);
             const strVal = Field.toString(fval as Field) || "";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fbb291858..78de1fd89 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, reaction, runInAction, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap, selectAll } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -432,6 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.ProseRef = ele;
+        this.setupEditor(this.config, this.props.fieldKey);
         this._dropDisposer?.();
         ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
         // if (this.autoHeight) this.tryUpdateScrollHeight(); 
@@ -573,12 +574,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const cm = ContextMenu.Instance;
 
         const changeItems: ContextMenuProps[] = [];
-        changeItems.push({ description: "plain", event: undoBatch(() => Doc.setNativeView(this.rootDoc)), icon: "eye" });
+        changeItems.push({
+            description: "plain", event: undoBatch(() => {
+                Doc.setNativeView(this.rootDoc);
+                this.layoutDoc.autoHeightMargins = undefined;
+            }), icon: "eye"
+        });
+        changeItems.push({
+            description: "metadata", event: undoBatch(() => {
+                this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
+                this.rootDoc.layoutKey = "layout_meta";
+                setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50);
+            }), icon: "eye"
+        });
         const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
         DocListCast(noteTypesDoc?.data).forEach(note => {
             const icon: IconProp = StrCast(note.icon) as IconProp;
             changeItems.push({
                 description: StrCast(note.title), event: undoBatch(() => {
+                    this.layoutDoc.autoHeightMargins = undefined;
                     Doc.setNativeView(this.rootDoc);
                     DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
                 }), icon: icon
@@ -864,8 +878,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         );
 
-        this.setupEditor(this.config, this.props.fieldKey);
-
         this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
             search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
             { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
@@ -1144,8 +1156,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
         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!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
-            this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+        if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+            this._editorView.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
         }
     }
 
@@ -1487,7 +1499,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
             <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
                 style={{
-                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${"- 17px"}))`,
+                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
                     backgroundColor: backgroundColor,
                     color: color,
                     opacity: annotated ? 1 : undefined
@@ -1555,7 +1567,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const selPad = Math.min(margins, 10);
         const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
         const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
-        return (
+        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) :
             <div className="formattedTextBox-cont"
                 onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                 style={{
@@ -1564,7 +1577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     width: this.props.dontScale ? undefined : `${100 / scale}%`,
                     height: this.props.dontScale ? undefined : `${100 / scale}%`,
                     // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
-                    ...this.styleFromLayoutString(scale)   // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+                    ...styleFromString
                 }}>
                 <div className={`formattedTextBox-cont`} ref={this._ref}
                     style={{
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index c94e93541..8afa0f6b5 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -2,6 +2,7 @@
 
 .button-dropdown-wrapper {
     position: relative;
+    display: flex;
 
     .dropdown-button {
         width: 15px;
diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss
index b7afb26a5..6990bdcf1 100644
--- a/src/client/views/pdf/AnchorMenu.scss
+++ b/src/client/views/pdf/AnchorMenu.scss
@@ -4,6 +4,35 @@
     padding: 5px;
     grid-template-columns: 90px 20px 90px;
 }
+.anchorMenu-highlighter {
+    padding-right: 5px;
+    .antimodeMenu-button  {
+        padding: 0;
+        padding: 0;
+        padding-right: 0px;
+        padding-left: 0px;
+        width: 5px;
+    }
+}
+.anchor-color-preview-button {  
+    width: 25px !important;  
+    .anchor-color-preview {
+        display: flex;
+        flex-direction:  column;
+        padding-right: 3px;
+        width: unset !important;
+        .color-preview {
+            width: 60%;
+            top: 80%;
+            height: 4px;
+            position: relative;
+            top: unset;
+            width: 15px;
+            margin-top: 5px;
+            display: block;
+        }
+    }
+}
 
 .color-wrapper {
     display: flex;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 3bf1f0828..3ba427c29 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
     @observable private _showLinkPopup: boolean = false;
 
-    @observable public _colorBtn = false;
     @observable public Highlighting: boolean = false;
     @observable public Status: "marquee" | "annotation" | "" = "";
 
@@ -97,9 +96,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @computed get highlighter() {
         const button =
-            <button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
-                <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
-                <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+            <button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
+                <div className="anchor-color-preview" >
+                    <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+                    <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+                </div>
             </button>;
 
         const dropdownContent =
@@ -117,7 +118,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             </div>;
         return (
             <Tooltip key="highlighter" title={<div className="dash-tooltip">{"Click to Highlight"}</div>}>
-                <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+                <div className="anchorMenu-highlighter">
+                    <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+                </div>
             </Tooltip>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From c9c9d00b581bc7f80edbf7848a8ffb3a8e64f005 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 10 Sep 2021 10:14:04 -0400
Subject: event handling fixes for ink.  double click on deselected ink takes
 shows handles.  lightbox view works.  transparent fills are not selectable.

---
 src/client/util/InteractionUtils.tsx               |  2 +-
 src/client/views/InkingStroke.tsx                  | 48 ++++++++++++++++------
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            |  8 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 5 files changed, 41 insertions(+), 21 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index e009fb3a9..8429a806a 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -209,7 +209,7 @@ export namespace InteractionUtils {
                 points={strpts}
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
-                    fill: fill ? fill : "none",
+                    fill: fill && fill !== "transparent" ? fill : "none",
                     opacity: 1.0,
                     // opacity: strokeWidth !== width ? 0.5 : undefined,
                     pointerEvents: pevents as any,
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index db09849fd..dc2a5b39b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,26 +1,26 @@
 import React = require("react");
-import { action, observable } from "mobx";
+import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
-import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { Scripting } from "../util/Scripting";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
-import "./InkStroke.scss";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { GestureOverlay } from "./GestureOverlay";
+import { Colors } from "./global/globalEnums";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
-import { Colors } from "./global/globalEnums";
-import { GestureOverlay } from "./GestureOverlay";
+import "./InkStroke.scss";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -29,6 +29,8 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
+    _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
+    _selDisposer: IReactionDisposer | undefined;
 
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
@@ -36,6 +38,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._properties = InkStrokeProperties.Instance;
     }
 
+    componentDidMount() {
+        this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+            selected => !selected && this.toggleControlButton());
+    }
+    componentWillUnmount() {
+        this._selDisposer?.();
+    }
+
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -53,14 +63,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
 
+    onClick = (e: React.MouseEvent) => {
+        if (this._handledClick) {
+            e.stopPropagation(); //stop the event so that docView won't open the lightbox
+        }
+    }
+
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
      */
     onPointerDown = (e: React.PointerEvent) => {
+        this._handledClick = false;
         if (this.props.isSelected(true)) {
             setupMoveUpEvents(this, e, returnFalse, emptyFunction,
-                action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                    doubleTap && this._properties && (this._properties._controlButton = true))
+                action((e: PointerEvent, doubleTap: boolean | undefined) => {
+                    doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
+                    if (doubleTap && this._properties) {
+                        this._properties._controlButton = true;
+                        this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+                    }
+                }), this._properties?._controlButton, this._properties?._controlButton
             );
         }
     }
@@ -77,7 +99,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     render() {
         TraceMobx();
-        this.toggleControlButton();
         // Extracting the ink data and formatting information of the current ink stroke.
         // console.log(InkingStroke.InkShape);
         const InkShape = GestureOverlay.Instance.InkShape;
@@ -115,12 +136,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         return (
             <svg className="inkStroke"
                 style={{
-                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
+                    pointerEvents: "none",
                     transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                     overflow: "visible",
                 }}
                 onPointerDown={this.onPointerDown}
+                onClick={this.onClick}
                 onContextMenu={() => {
                     const cm = ContextMenu.Instance;
                     if (cm) {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fad905d6d..dbab5e762 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -113,7 +113,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     scaling?: () => number,
     setHeight: (height: number) => void,
     layoutKey: string,
-    hideOnLeave?: boolean,
 }> {
     @computed get layout(): string {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8b19fb204..a2d2f17b6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -808,7 +808,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: (this.props.contentPointerEvents as any) || (this.isContentActive() ? "all" : "none"),
+                pointerEvents: this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none",
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
@@ -943,13 +943,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const showTitleHover = this.ShowTitle?.includes(":hover");
         const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
         const captionView = !showCaption ? (null) :
-            <div className="documentView-captionWrapper">
+            <div className="documentView-captionWrapper"
+                style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, }}>
                 <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={showCaption}
                     fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)}
-                    hideOnLeave={true}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
                     isContentActive={this.isContentActive}
@@ -964,7 +964,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 height: this.titleHeight,
                 color: lightOrDark(background),
                 background,
-                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 78de1fd89..e7a44f113 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -69,7 +69,6 @@ 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
-    hideOnLeave?: boolean;  // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
     xPadding?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yPadding?: number;
     noSidebar?: boolean;
@@ -1558,7 +1557,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         TraceMobx();
         const selected = this.props.isSelected();
         const active = this.props.isContentActive();
-        const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
-- 
cgit v1.2.3-70-g09d2

