From 917ebb4a7b6840f57abbf8dbcc197919f3ee3aac Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Wed, 18 Aug 2021 17:27:07 -0400
Subject: updates towards a menu

---
 src/client/views/nodes/DocumentView.tsx | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a29f2f662..ae3107b08 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -48,6 +48,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { ScriptingBox } from "./ScriptingBox";
 import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { ColorScheme } from "../../util/SettingsManager";
 const { Howl } = require('howler');
 
 interface Window {
@@ -132,7 +133,7 @@ export interface DocumentViewSharedProps {
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
     freezeDimensions?: boolean;
-    hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected 
+    hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
     hideTitle?: boolean;  // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
     hideDecorationTitle?: boolean;  // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
     treeViewDoc?: Doc;
@@ -420,13 +421,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     focus = (anchor: Doc, options?: DocFocusOptions) => {
         LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
-        // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
-        Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
-            this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
-        });
+              // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+              Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+                this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+            });        
         // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
-        const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
+        const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
@@ -764,7 +765,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
             cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
         }
-
+        
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
         DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
@@ -949,12 +950,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     @computed get renderDoc() {
         TraceMobx();
+        const isButton: boolean = this.props.Document.type === DocumentType.FONTICON;
         if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
         return this.docContents ??
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 id={this.props.Document[Id]}
                 style={{
-                    background: this.backgroundColor,
+                    background: isButton ? undefined : this.backgroundColor,
                     opacity: this.opacity,
                     color: StrCast(this.layoutDoc.color, "inherit"),
                     fontFamily: StrCast(this.Document._fontFamily, "inherit"),
@@ -970,7 +972,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     render() {
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = (CurrentUserUtils.ActiveDashboard?.darkScheme ?
+        const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
             ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
         const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
@@ -982,6 +984,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
         const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
             this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
+
+        // Return surrounding highlight 
         return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
             onContextMenu={this.onContextMenu}
             onKeyDown={this.onKeyDown}
@@ -1142,14 +1146,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         TraceMobx();
         const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
         const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (<div className="contentFittingDocumentView">
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
                     style={{
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
-                        transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                        width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
-                        height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+                        transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
+                        width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+                        height: isButton ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
                     <DocumentViewInternal {...this.props}
-- 
cgit v1.2.3-70-g09d2


From 6376a4b6d6a730c6c170fa25dd5f19dea6b8f82a Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Wed, 25 Aug 2021 05:20:50 -0400
Subject: many updates

---
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |  73 ++++---
 src/client/util/LinkManager.ts                     |   7 +-
 src/client/util/Scripting.ts                       |   6 -
 src/client/views/EditableView.tsx                  |   2 +
 src/client/views/GestureOverlay.tsx                |   5 +-
 src/client/views/InkingStroke.tsx                  |   4 +-
 src/client/views/MainView.tsx                      |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collectionLinearView/CollectionLinearView.tsx  |   3 +-
 src/client/views/nodes/DocumentView.tsx            |  12 +-
 src/client/views/nodes/button/FontIconBox.scss     |   3 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 238 ++++++++++++++-------
 .../views/nodes/formattedText/RichTextMenu.tsx     |   1 +
 src/fields/InkField.ts                             |   6 +-
 15 files changed, 234 insertions(+), 133 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1c4b5218c..f3a472329 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -698,14 +698,14 @@ export namespace Docs {
             return linkDoc;
         }
 
-        export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+        export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
             const I = new Doc();
             I[Initializing] = true;
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
             I.fillColor = fillColor;
-            I.strokeWidth = Number(strokeWidth);
+            I.strokeWidth = strokeWidth;
             I.strokeBezier = strokeBezier;
             I.strokeStartMarker = arrowStart;
             I.strokeEndMarker = arrowEnd;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 20274672a..7d1cbebdf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -946,21 +946,19 @@ export class CurrentUserUtils {
                 title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
                 list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia",
                     "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"],
-                script: 'changeFont'
+                script: 'setFont'
             },
-            { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'changeFontSize'},
+            { 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: "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:'changeAlignment("left")' },
-            { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", ignoreClick: true, script:'changeAlignment("center")' },
-            { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", ignoreClick: true, script:'changeAlignment("right")' },
-
-            { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script:'changeFontColor' },
-            { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, script:'changeHighlightColor' },
+            { 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")' },
         ];
         return tools;
     }
@@ -971,8 +969,10 @@ export class CurrentUserUtils {
             { 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: "fill-drip", click: 'setActiveInkTool("line")', script: 'setActiveInkTool("line" , true)' },
+            { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', script: '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;
     }
@@ -1015,18 +1015,18 @@ export class CurrentUserUtils {
                     CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid],
-                script: 'changeView',
-            },
+                script: 'setView',
+            }, // Always show
             {
                 title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
-                script: "changeBackgroundColor"
-            },
-            { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', script:'toggleOverlay'},
-            { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" },
-            { title: "Text", type: "textTools", subMenu: true },
-            { title: "Ink & GFX", type: "inkTools", subMenu: true },
-            { title: "Web", type: "webTools", subMenu: true },
-            { title: "Schema", type: "schemaTools", subMenu: true }
+                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: "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 & GFX", type: "inkTools", subMenu: true, expanded:'selectedDocumentType("ink")' }, // Always available
+            { 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
         ];
     }
 
@@ -1034,7 +1034,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 }) => {
+        (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => {
             const menuDocList: Doc[] = [];
             if (subMenu) {
                 // default is textTools
@@ -1083,7 +1083,17 @@ export class CurrentUserUtils {
                         onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined
                     }));
                 });
-                docList.push(CurrentUserUtils.linearButtonList({ linearViewSubMenu: true, flexGap: 5, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, menuDocList));
+                docList.push(CurrentUserUtils.linearButtonList({ 
+                    linearViewSubMenu: true, 
+                    flexGap: 5, 
+                    ignoreClick: true, 
+                    linearViewExpandable: true, 
+                    icon:title, 
+                    _height: 30, 
+                    backgroundColor: "transparent",
+                    linearViewIsExpanded: expanded ? ComputedField.MakeFunction(expanded) as any : undefined,
+                    hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
+                }, menuDocList));
             } else {
                 docList.push(Docs.Create.FontIconDocument({
                     _nativeWidth: width ? width : 25,
@@ -1105,6 +1115,7 @@ export class CurrentUserUtils {
                     color: Colors.WHITE,
                     backgroundColor: "transparent",
                     _dropAction: "alias",
+                    hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
                     _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
                     onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined
                 }));
@@ -1177,7 +1188,7 @@ export class CurrentUserUtils {
         }
         if (doc.myImportPanel === undefined) {
             const uploads = Cast(doc.myImportDocs, Doc, null);
-            const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", type: ButtonType.ToolButton, icon: "upload", system: true });
+            const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ToolButton, icon: "upload", system: true });
             doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" }));
         }
     }
@@ -1257,8 +1268,8 @@ export class CurrentUserUtils {
         doc._raiseWhenDragged = true;
         doc._showLabel = false;
         doc._showMenuLabel = true;
-        doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
-        doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
+        doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0, 0)");
+        doc.activeInkWidth = NumCast(doc.activeInkWidth, 1);
         doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
         doc.activeFillColor = StrCast(doc.activeFillColor, "");
         doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
@@ -1279,7 +1290,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);
+        this.setupImportSidebar(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
@@ -1567,3 +1578,15 @@ Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
     }
     return [];
 });
+Scripting.addGlobal(function selectedDocumentType(docType?:DocumentType, colType?:CollectionViewType, checkParent?:boolean) {
+    let selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    if (selected && checkParent){
+        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;
+    else return true;
+});
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3579083e4..219c83b12 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -107,7 +107,12 @@ export class LinkManager {
 
     public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
     public getAllDirectLinks(anchor: Doc): Doc[] {
-        return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
+        // FIXME:glr Why is Doc undefined?
+        if (Doc.GetProto(anchor)[DirectLinksSym]){
+            return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
+        } else {
+            return [];
+        }
     } // finds all links that contain the given anchor
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index aa7c23527..efee37419 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -171,18 +171,12 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
             if (!options.editable) {
                 batch = Doc.MakeReadOnly();
             }
-            const result1 = compiledFunction.apply(thisParam, params);
             
             const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
             if (batch) {
                 batch.end();
             }
 
-            if (script.includes('toggleOverlay(true)')){
-                console.log("[Scripting.ts] r1: ", result1);
-                console.log("[Scripting.ts]: ", result);
-            }
-
             return { success: true, result };
         } catch (error) {
 
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d5f8d3fcf..83336c180 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -155,6 +155,8 @@ export class EditableView extends React.Component<EditableProps> {
         return wasFocused !== this._editing;
     }
 
+    
+
     renderEditor() {
         console.log("render editor", this.props.autosuggestProps);
         return this.props.autosuggestProps
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index bbf21f22c..24f9501ce 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -14,6 +14,7 @@ import { DocUtils } from "../documents/Documents";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { Scripting } from "../util/Scripting";
+import { SelectionManager } from "../util/SelectionManager";
 import { Transform } from "../util/Transform";
 import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 import "./GestureOverlay.scss";
@@ -23,7 +24,6 @@ import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
 import { Touchable } from "./Touchable";
 import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import { SelectionManager } from "../util/SelectionManager";
 
 @observer
 export class GestureOverlay extends Touchable {
@@ -588,8 +588,9 @@ export class GestureOverlay extends Touchable {
                 this.makePolygon(this.InkShape, false);
                 this.dispatchGesture(GestureUtils.Gestures.Stroke);
                 this._points = [];
-                if (!CollectionFreeFormViewChrome.Instance._keepPrimitiveMode) {
+                if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
                     this.InkShape = "";
+                    Doc.UserDoc().activeInkTool = InkTool.None;
                 }
             }
             // if we're not drawing in a toolglass try to recognize as gesture
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 5fc159f14..db09849fd 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -5,7 +5,7 @@ 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 } from "../../fields/Types";
+import { Cast, StrCast, NumCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
@@ -168,7 +168,7 @@ export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activ
 export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
 export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
 export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkWidth(): number { return NumCast(ActiveInkPen()?.activeInkWidth); }
 export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
 Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
     CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 75c57909d..dcdf6b9c3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -447,7 +447,6 @@ export class MainView extends React.Component {
         this._panelContent = "none";
         this._sidebarContent.proto = undefined;
         this._flyoutWidth = 0;
-        console.log("close flyout");
     });
 
     remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d09d9b9d7..3c5a63c77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -506,7 +506,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
-                    { title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
+                    { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
index 990c2e4b2..5c16f8929 100644
--- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
@@ -147,8 +147,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                     {this.childLayoutPairs.map((pair, ind) => {
                         const nested = pair.layout._viewType === CollectionViewType.Linear;
                         const dref = React.createRef<HTMLDivElement>();
+                        const hidden = pair.layout.hidden === true;
                         // const scalable = pair.layout.onClick || pair.layout.onDragStart;
-                        return <div className={`collectionLinearView-docBtn`} key={pair.layout[Id]} ref={dref}
+                        return hidden ? (null) : <div className={`collectionLinearView-docBtn`} key={pair.layout[Id]} ref={dref}
                             style={{
                                 pointerEvents: "all",
                                 width: nested ? undefined : NumCast(pair.layout._width),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6ab25dd6a..5d9f9c6db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { AudioField } from "../../../fields/URLField";
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils, returnTrue } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -25,6 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
+import { ColorScheme } from "../../util/SettingsManager";
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
@@ -41,14 +42,13 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView
 import { DocumentContentsView } from "./DocumentContentsView";
 import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { LinkDocPreview } from "./LinkDocPreview";
-import { PresBox } from './trails/PresBox';
 import { RadialMenu } from './RadialMenu';
-import React = require("react");
 import { ScriptingBox } from "./ScriptingBox";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
-import { ColorScheme } from "../../util/SettingsManager";
+import { PresBox } from './trails/PresBox';
+import React = require("react");
 const { Howl } = require('howler');
 
 interface Window {
@@ -795,7 +795,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), // || (CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || 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/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index dfb549b95..8de97ea89 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -167,6 +167,7 @@
             position: absolute;
             width: fit-content;
             height: fit-content;
+            color: black;
             top: 100%;
             z-index: 21;
             background-color: #e3e3e3;
@@ -316,7 +317,7 @@
         }
     }
 
-    &.editableTxt {
+    &.editableText {
         cursor: pointer;
         background: transparent;
         width: 100%;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 744064b9f..53cd017b0 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -25,7 +25,9 @@ import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
 import { RichTextMenu } from '../formattedText/RichTextMenu';
 import './FontIconBox.scss';
-import { SetActiveInkWidth } from '../../InkingStroke';
+import { SetActiveInkWidth, SetActiveInkColor } from '../../InkingStroke';
+import { GestureOverlay } from '../../GestureOverlay';
+import { WebField } from '../../../../fields/URLField';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -40,7 +42,7 @@ export enum ButtonType {
     ColorButton = "colorBtn",
     ToolButton = "toolBtn",
     NumberButton = "numBtn",
-    EditableText = "editableTxt"
+    EditableText = "editableText"
 }
 
 export enum NumButtonType {
@@ -115,6 +117,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             const dropdown =
                 <div
                     className="menuButton-dropdownBox"
+                    onPointerDown={e => e.stopPropagation()}
                     style={{ left: 0 }}
                 >
                     <input type="range" step="1" min="0" max="100" value={checkResult}
@@ -240,7 +243,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         let icon: IconProp = "caret-down";
 
 
-        if (script === 'changeView') {
+        if (script === 'setView') {
             const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
             if (selected && StrCast(selected.Document.type) === DocumentType.COL) {
                 text = StrCast(selected.Document._viewType);
@@ -249,23 +252,24 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 text = StrCast(selected.Document.type);
                 icon = Doc.toIcon(selected.Document);
             } else {
+                dropdown = false;
                 text = "None selected";
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
-        } else if (script === 'changeFont') {
+        } else if (script === 'setFont') {
             const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
             if (selected && StrCast(selected.Document.type) === DocumentType.RTF) {
                 text = StrCast(selected.Document._fontFamily);
             } else {
                 const fontFamily = StrCast(Doc.UserDoc()._fontFamily);
-                console.log(fontFamily);
                 text = fontFamily;
             }
             noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
                 "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
         }
 
-        const items = this.buttonList.map((value) => {
+        // Get items to place into the list
+        const list = this.buttonList.map((value) => {
             if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
                 return;
             }
@@ -277,7 +281,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             };
             return <div className="list-item" key={`${value}`}
                 style={{
-                    fontFamily: script === 'changeFont' ? value : undefined,
+                    fontFamily: script === 'setFont' ? value : undefined,
                     backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined
                 }}
                 onClick={click}>
@@ -292,22 +296,21 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
         return (
             <div className={`menuButton ${this.type} ${active}`}
-                style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
+                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} />}
                 <div className="menuButton-dropdown-header">
                     {text && text[0].toUpperCase() + text.slice(1)}
                 </div>
                 {label}
-                <div
-                    className="menuButton-dropDown"
-                    style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+                {!dropdown ? (null) : <div className="menuButton-dropDown">
                     <FontAwesomeIcon icon={icon} color={color} size="sm" />
-                </div>
+                </div>}
                 {this.rootDoc.dropDownOpen ?
                     <div>
                         <div className="menuButton-dropdownList"
                             style={{ left: 0 }}>
-                            {items}
+                            {list}
                         </div>
                         <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
                     </div>
@@ -323,29 +326,38 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         const active: string = StrCast(this.rootDoc.dropDownOpen);
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const numSelected = SelectionManager.Views().length;
-        const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined;
 
         const script: string = StrCast(this.rootDoc.script);
         const scriptCheck: string = script + "(undefined, true)";
         const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
-        console.log("[boolResul]: ", boolResult);
+
+        let stroke: boolean = false;
+        let strokeIcon: any;
+        if (script === "setStrokeColor"){
+            stroke = true;
+            const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
+            strokeIcon = (<div style={{borderRadius: "100%", width: Number(checkWidth)+'%', height: Number(checkWidth)+'%', backgroundColor: boolResult }}/>)
+        }
 
         const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
             '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
-            '#FFFFFF', '#f1efeb', 'transparent'];
+            '#FFFFFF', '#f1efeb'];
 
-        const colorBox = (func: (color: ColorState) => void) => <SketchPicker onChange={func} color={selectedDoc ? selectedDoc._backgroundColor : backgroundColor}
+        const colorBox = (func: (color: ColorState) => void) => <SketchPicker 
+            disableAlpha={!stroke}
+            onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
             presetColors={colorOptions} />;
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
                 {this.label}
             </div>;
+
         const dropdownCaret = <div
             className="menuButton-dropDown"
             style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
             <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
         </div>;
+
         const click = (value: ColorState) => {
             const hex: string = value.hex;
             const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
@@ -358,10 +370,10 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
                 onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}
                 onPointerDown={e => e.stopPropagation()}>
-                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+                {stroke ? strokeIcon : <><FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
                 <div className="colorButton-color"
-                    style={{ backgroundColor: selectedDoc ? selectedDoc._backgroundColor : backgroundColor }}
-                ></div>
+                    style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }}
+                ></div></>}
                 {label}
                 {/* {dropdownCaret} */}
                 {this.rootDoc.dropDownOpen ?
@@ -408,7 +420,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
                 {this.label}
             </div>;
-        console.log("switchToggle", switchToggle);
+
         if (switchToggle) {
             return (
                 <div className={`menuButton ${this.type} ${'switch'}`}
@@ -457,17 +469,24 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
     }
 
     @computed get editableText() {
+        // Script for running the toggle
+        const script: string = StrCast(this.rootDoc.script);
+
+        // Script for checking the outcome of the toggle
+        let checkScript:string = StrCast(this.rootDoc.script) + "('', true)";
+
+        // Function to run the script
+        const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result;
+
         const setValue = (value: string, shiftDown?: boolean): boolean => {
-            console.log("setValue");
+            ScriptField.MakeScript(script + "('"+value+"')")?.script.run();
             return true;
         };
-
         return (
-            <div className={`menuButton ${this.type}`}>
-                HELLO
-                {/* <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
-                <EditableView GetValue={() => ""} SetValue={setValue} contents="...">
-                </EditableView> */}
+            <div className="menuButton editableText">
+                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
+                <EditableView GetValue={() => checkResult} SetValue={setValue} contents="...">
+                </EditableView>
             </div>
         );
     }
@@ -477,12 +496,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         // Variables called through eval (from button)
-        const canUndo: boolean = UndoManager.CanUndo();
-        const canRedo: boolean = UndoManager.CanRedo();
         const numSelected = SelectionManager.Views().length;
-        const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined;
-        const userDoc = Doc.UserDoc();
-
 
         const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark;
 
@@ -503,6 +517,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             case ButtonType.EditableText:
                 console.log("Editable text");
                 button = this.editableText;
+                break;
             case ButtonType.NumberButton:
                 button = this.numberButton;
                 break;
@@ -534,14 +549,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                     </div>
                 );
                 break;
-            case ButtonType.DoubleButton:
-                button = (
-                    <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}>
-                        <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                        {label}
-                    </div>
-                );
-                break;
             case ButtonType.MenuButton:
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}>
@@ -554,7 +561,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 break;
         }
 
-        return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton ? button :
+        return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
             <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
                 {button}
             </Tooltip>;
@@ -563,18 +570,25 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
 
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function changeView(view: string) {
+Scripting.addGlobal(function setView(view: string) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
     selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
 });
 
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function changeBackgroundColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) {
+    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
     if (checkResult) {
-        return selected && selected.Document._backgroundColor;
+        if (selected) {
+            console.log("[Background] (selected): " + StrCast(selected._backgroundColor));
+            return selected._backgroundColor;
+        } else {
+            return "#FFFFFF";
+        }
     }
-    selected ? selected.Document._backgroundColor = color : console.log("[FontIconBox.tsx] changeBackgroundColor failed");
+    if (selected && selected.type === DocumentType.INK) selected.fillColor = color;
+    if (selected) selected._backgroundColor = color;
+    Doc.UserDoc()._fontColor = color;
 });
 
 // toggle: Set overlay status of selected document
@@ -586,52 +600,58 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
 });
 
+/** TEXT
+ *      setFont
+ *      setFontSize
+ *      toggleBold
+ *      toggleUnderline
+ *      toggleItalic
+ **/ 
+
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function changeFont(font: string) {
+Scripting.addGlobal(function setFont(font: string) {
     SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font);
     Doc.UserDoc()._fontFamily = font;
-    console.log(Doc.UserDoc()._fontFamily);
     return Doc.UserDoc()._fontFamily;
 });
 
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function changeFontColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) {
+    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
     if (checkResult) {
         if (selected) {
-            console.log("[Font color] (selected): " + StrCast(selected.Document._fontColor));
-            return selected.Document._fontColor;
+            console.log("[Font color] (selected): " + StrCast(selected._fontColor));
+            return selected._fontColor;
         } else {
             console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor));
             return Doc.UserDoc()._fontColor;
         }
     }
+    if (selected) selected._fontColor = color;
     Doc.UserDoc()._fontColor = color;
 });
 
-// toggle: Set overlay status of selected document
-Scripting.addGlobal(function changeFontHighlight(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    if (checkResult) {
-        if (selected) {
-            console.log("[Font color] (selected): " + StrCast(selected.Document._fontColor));
-            return selected.Document._fontColor;
-        } else {
-            console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor));
-            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;
+//     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 changeFontSize(size: string, checkResult?: boolean) {
+Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) {
     if (checkResult) {
         const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10);
         return size;
     }
-    console.log(size);
     Doc.UserDoc()._fontSize = size + "px";
 });
 
@@ -639,7 +659,7 @@ Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
     if (checkResult) {
         return Doc.UserDoc().bold;
     }
-    SelectionManager.Views().map(dv => dv.props.Document.bold = !dv.props.Document.bold);
+    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;
     return Doc.UserDoc().bold;
 });
@@ -648,8 +668,8 @@ Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
     if (checkResult) {
         return Doc.UserDoc().underline;
     }
-    SelectionManager.Views().map(dv => dv.props.Document.underline = !dv.props.Document.underline);
-    Doc.UserDoc().bold = !Doc.UserDoc().underline;
+    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;
     return Doc.UserDoc().underline;
 });
 
@@ -657,17 +677,36 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
     if (checkResult) {
         return Doc.UserDoc().italic;
     }
-    SelectionManager.Views().map(dv => dv.props.Document.italic = !dv.props.Document.italic);
-    Doc.UserDoc().bold = !Doc.UserDoc().italic;
+    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;
     return Doc.UserDoc().italic;
 });
 
-Scripting.addGlobal(function setActiveInkTool(tool: InkTool, checkResult?: boolean) {
+
+
+
+/** INK
+ *      setActiveInkTool
+ *      setStrokeWidth
+ *      setStrokeColor
+ **/ 
+
+Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) {
     if (checkResult) {
-        return Doc.UserDoc().activeInkTool === tool;
+        return Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool;
     }
-    if (tool) {
-        if (Doc.UserDoc().activeInkTool === tool){
+    if (tool === "circle") {
+        Doc.UserDoc().activeInkTool = "pen";
+        GestureOverlay.Instance.InkShape = tool;
+    } else if (tool === "square") {
+        Doc.UserDoc().activeInkTool = "pen";
+        GestureOverlay.Instance.InkShape = tool;
+    } else if (tool === "line") {
+        Doc.UserDoc().activeInkTool = "pen";
+        GestureOverlay.Instance.InkShape = tool;
+    } else if (tool) {
+        if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool){
+            GestureOverlay.Instance.InkShape = "";
             Doc.UserDoc().activeInkTool = InkTool.None;
         } else {
             Doc.UserDoc().activeInkTool = tool;
@@ -679,9 +718,44 @@ Scripting.addGlobal(function setActiveInkTool(tool: InkTool, checkResult?: boole
 
 Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) {
     if (checkResult) {
-        const width: number = NumCast(Doc.UserDoc().activeInkWidth);
-        return width;
+        const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+        if (selected && selected.type === DocumentType.INK){
+            return Number(selected.strokeWidth);
+        } else {
+            const width: number = NumCast(Doc.UserDoc().activeInkWidth);
+            return width;
+        }
     }
-    SetActiveInkWidth(StrCast(width));
+    Doc.UserDoc().activeInkWidth = width;
     SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(width));
+});
+
+// toggle: Set overlay status of selected document
+Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolean) {
+    if (checkResult) {
+        const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+        if (selected && selected.type === DocumentType.INK){
+            return selected.color;
+        } else {
+            const color: string = StrCast(Doc.UserDoc().activeInkColor);
+            return color;
+        }
+    }
+    SetActiveInkColor(StrCast(color));
+    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.color = String(color));
+});
+
+
+/** WEB
+ *      webSetURL
+ **/ 
+Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) {
+    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    console.log("URL: ", url);
+    if (checkResult && selected && selected.type === DocumentType.WEB){
+        return Cast(selected.data, WebField, null).url;
+    }
+    else if (selected && selected.type === DocumentType.WEB){
+        selected.data = url;
+    }
 });
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 82ad2b7db..c081b8e26 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -277,6 +277,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
         state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+        console.log("Marks: " + found);
         return found;
     }
 
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 1270a2dab..f16e143d8 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -1,8 +1,8 @@
+import { createSimpleSchema, list, object, serializable } from "serializr";
+import { Scripting } from "../client/util/Scripting";
 import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
+import { Copy, ToScriptString, ToString } from "./FieldSymbols";
 import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString, Update } from "./FieldSymbols";
-import { Scripting } from "../client/util/Scripting";
 
 // Helps keep track of the current ink tool in use.
 export enum InkTool {
-- 
cgit v1.2.3-70-g09d2


From 4aec49631f44d0afb6125b240b504e441ff5cd5c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 25 Aug 2021 16:22:08 -0400
Subject: fixed events for forceActive documents

---
 src/client/views/nodes/DocumentView.tsx | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5d9f9c6db..0d396f8c4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -423,10 +423,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     focus = (anchor: Doc, options?: DocFocusOptions) => {
         LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
-              // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
-              Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
-                this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
-            });        
+        // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+        Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+            this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+        });
         // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
         const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
@@ -767,7 +767,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
             cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
         }
-        
+
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
         DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
@@ -780,7 +780,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     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;
+    isContentActive = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.Document.forceActive || this.props.isContentActive() ? true : false;
     @computed get contents() {
         TraceMobx();
         const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -795,7 +795,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) || (CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || this.isContentActive() ? "all" : "none"),
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
-- 
cgit v1.2.3-70-g09d2


From eeb8b6b1e69c73b822b7e7a67829babb93ca0601 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Wed, 25 Aug 2021 21:07:37 -0400
Subject: mini updates

---
 src/client/util/CurrentUserUtils.ts                                 | 6 +++---
 .../views/collections/collectionLinearView/CollectionLinearView.tsx | 2 ++
 src/client/views/nodes/DocumentView.tsx                             | 2 +-
 3 files changed, 6 insertions(+), 4 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6653990bd..fbe216ef1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -923,7 +923,7 @@ export class CurrentUserUtils {
     }
 
     static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
-        ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true,
+        ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true,
         dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
         _lockedPosition: true, linearViewIsExpanded: true, system: true, flexDirection: "row"
     })) as any as Doc
@@ -941,7 +941,7 @@ export class CurrentUserUtils {
             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 });
         }
         if (doc.dockedBtns === undefined) {
-            doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 5, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
+            doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
         }
         (doc["dockedBtn-undo"] as Doc).dontUndo = true;
         (doc["dockedBtn-redo"] as Doc).dontUndo = true;
@@ -1093,7 +1093,7 @@ export class CurrentUserUtils {
                 });
                 docList.push(CurrentUserUtils.linearButtonList({
                     linearViewSubMenu: true,
-                    flexGap: 5,
+                    flexGap: 0,
                     ignoreClick: true,
                     linearViewExpandable: true,
                     icon: title,
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
index 5c16f8929..74d581946 100644
--- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx
@@ -154,6 +154,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 pointerEvents: "all",
                                 width: nested ? undefined : NumCast(pair.layout._width),
                                 height: nested ? undefined : NumCast(pair.layout._height),
+                                marginLeft: !nested ? 2.5 : 0,
+                                marginRight: !nested ? 2.5 : 0,
                                 // width: NumCast(pair.layout._width),
                                 // height: NumCast(pair.layout._height),
                             }}  >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0d396f8c4..ee3266647 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -795,7 +795,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),// || (CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || this.isContentActive() ? "all" : "none"),
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
-- 
cgit v1.2.3-70-g09d2


From 8beb8fa42ba5f84bb13b5877560fc92ad3613e88 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Wed, 25 Aug 2021 21:34:40 -0400
Subject: basic audio trim complete

---
 src/client/documents/Documents.ts                  |    2 +-
 src/client/util/CurrentUserUtils.ts                |    2 +-
 src/client/views/AudioWaveform.tsx                 |  214 ++-
 src/client/views/DocumentDecorations.tsx           |   11 +-
 .../collections/CollectionStackedTimeline.scss     |   22 +-
 .../collections/CollectionStackedTimeline.tsx      | 1550 ++++++++++----------
 src/client/views/nodes/AudioBox.scss               |   54 +-
 src/client/views/nodes/AudioBox.tsx                | 1289 ++++++++--------
 src/client/views/nodes/DocumentView.tsx            |    1 +
 src/client/views/nodes/LabelBox.tsx                |   21 +-
 10 files changed, 1615 insertions(+), 1551 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 72b52616a..fce5e76f5 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -356,7 +356,7 @@ export namespace Docs {
             }],
             [DocumentType.AUDIO, {
                 layout: { view: AudioBox, dataField: defaultDataKey },
-                options: { _height: 35, backgroundColor: "lightGray", links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _height: 100, backgroundColor: "lightGray", links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.PDF, {
                 layout: { view: PDFBox, dataField: defaultDataKey },
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 62fab1b23..bf768a401 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -443,7 +443,7 @@ export class CurrentUserUtils {
             (doc.emptyWall as Doc).videoWall = true;
         }
         if (doc.emptyAudio === undefined) {
-            doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
+            doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
             ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
         }
         if (doc.emptyNote === undefined) {
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 313d0062e..f519f9ef0 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -9,132 +9,118 @@ import { listSpec } from "../../fields/Schema";
 import { Cast, NumCast } from "../../fields/Types";
 import { numberRange } from "../../Utils";
 import "./AudioWaveform.scss";
+import { Colors } from "./global/globalEnums";
 
 export interface AudioWaveformProps {
-  duration: number;
-  mediaPath: string;
-  dataDoc: Doc;
-  trimming: boolean;
-  PanelHeight: () => number;
+    duration: number;
+    mediaPath: string;
+    layoutDoc: Doc;
+    trimming: boolean;
+    PanelHeight: () => number;
 }
 
 @observer
 export class AudioWaveform extends React.Component<AudioWaveformProps> {
-  public static NUMBER_OF_BUCKETS = 100;
-  @computed get _waveHeight() {
-    return Math.max(50, this.props.PanelHeight());
-  }
-  componentDidMount() {
-    const audioBuckets = Cast(
-      this.props.dataDoc.audioBuckets,
-      listSpec("number"),
-      []
-    );
-    if (!audioBuckets.length) {
-      this.props.dataDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
-      setTimeout(this.createWaveformBuckets);
+    public static NUMBER_OF_BUCKETS = 100;
+    @computed get _waveHeight() {
+        return Math.max(50, this.props.PanelHeight());
+    }
+    componentDidMount() {
+        const audioBuckets = Cast(
+            this.props.layoutDoc.audioBuckets,
+            listSpec("number"),
+            []
+        );
+        if (!audioBuckets.length) {
+            this.props.layoutDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
+            setTimeout(this.createWaveformBuckets);
+        }
     }
 
-    // const trimBuckets = Cast(
-    //     this.props.dataDoc.trimBuckets,
-    //     listSpec("number"),
-    //     []
-    //   );
-    //   if (!trimBuckets.length) {
-    //     this.props.dataDoc.trimBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
-    //     this.createTrimBuckets();
-    //   }
-  }
-  // decodes the audio file into peaks for generating the waveform
-  createWaveformBuckets = async () => {
-    axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
-      (response) => {
-        const context = new window.AudioContext();
-        context.decodeAudioData(
-          response.data,
-          action((buffer) => {
-            const decodedAudioData = buffer.getChannelData(0);
+    // decodes the audio file into peaks for generating the waveform
+    createWaveformBuckets = async () => {
+        axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
+            (response) => {
+                const context = new window.AudioContext();
+                context.decodeAudioData(
+                    response.data,
+                    action((buffer) => {
+                        const decodedAudioData = buffer.getChannelData(0);
 
-            const bucketDataSize = Math.floor(
-              decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
-            );
-            const brange = Array.from(Array(bucketDataSize));
-            this.props.dataDoc.audioBuckets = new List<number>(
-              numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
-                (i: number) =>
-                  brange.reduce(
-                    (p, x, j) =>
-                      Math.abs(
-                        Math.max(p, decodedAudioData[i * bucketDataSize + j])
-                      ),
-                    0
-                  ) / 2
-              )
-            );
-          })
+                        const bucketDataSize = Math.floor(
+                            decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
+                        );
+                        const brange = Array.from(Array(bucketDataSize));
+                        this.props.layoutDoc.audioBuckets = new List<number>(
+                            numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
+                                (i: number) =>
+                                    brange.reduce(
+                                        (p, x, j) =>
+                                            Math.abs(
+                                                Math.max(p, decodedAudioData[i * bucketDataSize + j])
+                                            ),
+                                        0
+                                    ) / 2
+                            )
+                        );
+                    })
+                );
+            }
         );
-      }
-    );
-  };
+    };
 
-  @action
-  createTrimBuckets = () => {
-    const audioBuckets = Cast(
-      this.props.dataDoc.audioBuckets,
-      listSpec("number"),
-      []
-    );
-
-    const start = Math.floor(
-      (NumCast(this.props.dataDoc.clipStart) / this.props.duration) * 100
-    );
-    const end = Math.floor(
-      (NumCast(this.props.dataDoc.clipEnd) / this.props.duration) * 100
-    );
-    return audioBuckets.slice(start, end);
-  };
+    @action
+    createTrimBuckets = () => {
+        const audioBuckets = Cast(
+            this.props.layoutDoc.audioBuckets,
+            listSpec("number"),
+            []
+        );
 
-  render() {
-    const audioBuckets = Cast(
-      this.props.dataDoc.audioBuckets,
-      listSpec("number"),
-      []
-    );
+        const start = Math.floor(
+            (NumCast(this.props.layoutDoc.clipStart) / this.props.duration) * 100
+        );
+        const end = Math.floor(
+            (NumCast(this.props.layoutDoc.clipEnd) / this.props.duration) * 100
+        );
+        return audioBuckets.slice(start, end);
+    };
 
-    const trimBuckets = Cast(
-      this.props.dataDoc.trimBuckets,
-      listSpec("number"),
-      []
-    );
+    render() {
+        const audioBuckets = Cast(
+            this.props.layoutDoc.audioBuckets,
+            listSpec("number"),
+            []
+        );
 
-    return (
-      <div className="audioWaveform">
-        {this.props.trimming ? (
-          <Waveform
-            color={"darkblue"}
-            height={this._waveHeight}
-            barWidth={0.1}
-            pos={this.props.duration}
-            duration={this.props.duration}
-            peaks={
-              audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS
-                ? audioBuckets
-                : undefined
-            }
-            progressColor={"blue"}
-          />
-        ) : (
-          <Waveform
-            color={"darkblue"}
-            height={this._waveHeight}
-            barWidth={0.1}
-            pos={this.props.duration}
-            duration={this.props.duration}
-            peaks={this.createTrimBuckets()}
-            progressColor={"blue"}
-          />
-        )}
-      </div>
-    );
-  }
+        return (
+            <div className="audioWaveform">
+                {this.props.trimming || !this.props.layoutDoc.clipEnd ? (
+                    <Waveform
+                        color={Colors.MEDIUM_BLUE}
+                        height={this._waveHeight}
+                        barWidth={0.1}
+                        pos={this.props.duration}
+                        duration={this.props.duration}
+                        peaks={
+                            audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS
+                                ? audioBuckets
+                                : undefined
+                        }
+                        progressColor={Colors.MEDIUM_BLUE}
+                    />
+                ) : (
+                    <Waveform
+                        color={Colors.MEDIUM_BLUE}
+                        height={this._waveHeight}
+                        barWidth={0.1}
+                        pos={this.props.duration}
+                        duration={this.props.duration}
+                        peaks={this.createTrimBuckets()}
+                        progressColor={Colors.MEDIUM_BLUE}
+                    />
+                )}
+            </div>
+        );
+    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d24ab974c..0424b4e63 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -389,10 +389,10 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 Doc.GetProto(doc).data = new InkField(inkPts.map(ipt =>  // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                    ({
-                        X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
-                        Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
-                    })));
+                ({
+                    X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+                    Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+                })));
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
@@ -402,6 +402,9 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
     get selectionTitle(): string {
         if (SelectionManager.Views().length === 1) {
             const selected = SelectionManager.Views()[0];
+            if (selected.ComponentView?.getTitle?.()) {
+                return selected.ComponentView.getTitle();
+            }
             if (this._titleControlString.startsWith("=")) {
                 return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
             }
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 92476e298..59c21210a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,9 +1,9 @@
+@import "../global/globalCssVariables.scss";
+
 .collectionStackedTimeline {
   position: absolute;
   width: 100%;
   height: 100%;
-  border: gray solid 1px;
-  border-radius: 3px;
   z-index: 1000;
   overflow: hidden;
   top: 0px;
@@ -11,19 +11,21 @@
   .collectionStackedTimeline-trim-shade {
     position: absolute;
     height: 100%;
-    background-color: black;
+    background-color: $dark-gray;
     opacity: 0.3;
   }
 
   .collectionStackedTimeline-trim-controls {
     height: 100%;
     position: absolute;
-    border: 2px solid yellow;
+    box-sizing: border-box;
+    border: 2px solid $medium-blue;
     display: flex;
     justify-content: space-between;
+    max-width: 100%;
 
     .collectionStackedTimeline-trim-handle {
-      background-color: yellow;
+      background-color: $medium-blue;
       height: 100%;
       width: 5px;
       cursor: ew-resize;
@@ -35,19 +37,19 @@
     width: 10px;
     top: 2.5%;
     height: 95%;
-    background: lightblue;
-    border-radius: 5px;
+    background: $light-blue;
+    border-radius: 3px;
     opacity: 0.3;
     z-index: 500;
     border-style: solid;
-    border-color: darkblue;
+    border-color: $medium-blue;
     border-width: 1px;
   }
 
   .collectionStackedTimeline-current {
     width: 1px;
     height: 100%;
-    background-color: red;
+    background-color: $pink;
     position: absolute;
     top: 0px;
     pointer-events: none;
@@ -64,7 +66,7 @@
 
     .collectionStackedTimeline-left-resizer,
     .collectionStackedTimeline-resizer {
-      background: dimgrey;
+      background: $medium-gray;
       position: absolute;
       top: 0;
       height: 100%;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 230238ba8..51e05e278 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,11 +1,11 @@
 import React = require("react");
 import {
-  action,
-  computed,
-  IReactionDisposer,
-  observable,
-  reaction,
-  runInAction,
+    action,
+    computed,
+    IReactionDisposer,
+    observable,
+    reaction,
+    runInAction,
 } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
@@ -16,14 +16,14 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
 import {
-  emptyFunction,
-  formatTime,
-  OmitKeys,
-  returnFalse,
-  returnOne,
-  setupMoveUpEvents,
-  StopEvent,
-  returnTrue,
+    emptyFunction,
+    formatTime,
+    OmitKeys,
+    returnFalse,
+    returnOne,
+    setupMoveUpEvents,
+    StopEvent,
+    returnTrue,
 } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { LinkManager } from "../../util/LinkManager";
@@ -35,798 +35,820 @@ import { AudioWaveform } from "../AudioWaveform";
 import { CollectionSubView } from "../collections/CollectionSubView";
 import { LightboxView } from "../LightboxView";
 import {
-  DocAfterFocusFunc,
-  DocFocusFunc,
-  DocumentView,
-  DocumentViewProps,
+    DocAfterFocusFunc,
+    DocFocusFunc,
+    DocumentView,
+    DocumentViewProps,
 } from "../nodes/DocumentView";
 import { LabelBox } from "../nodes/LabelBox";
 import "./CollectionStackedTimeline.scss";
+import { Colors } from "../global/globalEnums";
 
 type PanZoomDocument = makeInterface<[]>;
 const PanZoomDocument = makeInterface();
 export type CollectionStackedTimelineProps = {
-  duration: number;
-  Play: () => void;
-  Pause: () => void;
-  playLink: (linkDoc: Doc) => void;
-  playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
-  playing: () => boolean;
-  setTime: (time: number) => void;
-  startTag: string;
-  endTag: string;
-  mediaPath: string;
-  dictationKey: string;
-  trimming: boolean;
-  trimBounds: { start: number; end: number };
+    duration: number;
+    Play: () => void;
+    Pause: () => void;
+    playLink: (linkDoc: Doc) => void;
+    playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
+    playing: () => boolean;
+    setTime: (time: number) => void;
+    startTag: string;
+    endTag: string;
+    mediaPath: string;
+    dictationKey: string;
+    trimming: boolean;
+    trimStart: number;
+    trimEnd: number;
+    trimDuration: number;
+    setStartTrim: (newStart: number) => void;
+    setEndTrim: (newEnd: number) => void;
 };
 
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<
-  PanZoomDocument,
-  CollectionStackedTimelineProps
+    PanZoomDocument,
+    CollectionStackedTimelineProps
 >(PanZoomDocument) {
-  @observable static SelectingRegion:
-    | CollectionStackedTimeline
-    | undefined = undefined;
-  static RangeScript: ScriptField;
-  static LabelScript: ScriptField;
-  static RangePlayScript: ScriptField;
-  static LabelPlayScript: ScriptField;
-
-  private _timeline: HTMLDivElement | null = null;
-  private _markerStart: number = 0;
-  @observable _markerEnd: number = 0;
-
-  get minLength() {
-    const rect = this._timeline?.getBoundingClientRect();
-    if (rect) {
-      return 0.05 * this.duration;
+    @observable static SelectingRegion: CollectionStackedTimeline | undefined =
+        undefined;
+    static RangeScript: ScriptField;
+    static LabelScript: ScriptField;
+    static RangePlayScript: ScriptField;
+    static LabelPlayScript: ScriptField;
+
+    private _timeline: HTMLDivElement | null = null;
+    private _markerStart: number = 0;
+    @observable _markerEnd: number = 0;
+
+    get minLength() {
+        const rect = this._timeline?.getBoundingClientRect();
+        if (rect) {
+            return 0.05 * this.duration;
+        }
+        return 0;
     }
-    return 0;
-  }
-
-  get trimStart() {
-    return this.props.trimBounds.start;
-  }
-
-  get trimEnd() {
-    return this.props.trimBounds.end;
-  }
-
-  set trimStart(start: number) {
-    this.props.trimBounds.start = start;
-  }
-
-  set trimEnd(end: number) {
-    this.props.trimBounds.end = end;
-  }
-
-  get duration() {
-    return this.props.duration;
-  }
-  @computed get currentTime() {
-    return NumCast(this.layoutDoc._currentTimecode);
-  }
-  @computed get selectionContainer() {
-    return CollectionStackedTimeline.SelectingRegion !== this ? null : (
-      <div
-        className="collectionStackedTimeline-selector"
-        style={{
-          left: `${
-            (Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) /
-              this.duration) *
-            100
-          }%`,
-          width: `${
-            (Math.abs(this._markerStart - this._markerEnd) / this.duration) *
-            100
-          }%`,
-        }}
-      />
-    );
-  }
-
-  constructor(props: any) {
-    super(props);
-    // onClick play scripts
-    CollectionStackedTimeline.RangeScript =
-      CollectionStackedTimeline.RangeScript ||
-      ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
-        self: Doc.name,
-        scriptContext: "any",
-        clientX: "number",
-      })!;
-    CollectionStackedTimeline.RangePlayScript =
-      CollectionStackedTimeline.RangePlayScript ||
-      ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
-        self: Doc.name,
-        scriptContext: "any",
-        clientX: "number",
-      })!;
-  }
-
-  componentDidMount() {
-    document.addEventListener("keydown", this.keyEvents, true);
-  }
-  componentWillUnmount() {
-    document.removeEventListener("keydown", this.keyEvents, true);
-    if (CollectionStackedTimeline.SelectingRegion === this)
-      runInAction(
-        () => (CollectionStackedTimeline.SelectingRegion = undefined)
-      );
-  }
-
-  anchorStart = (anchor: Doc) =>
-    NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
-  anchorEnd = (anchor: Doc, val: any = null) => {
-    const endVal = NumCast(anchor[this.props.endTag], val);
-    return NumCast(
-      anchor._timecodeToHide,
-      endVal === undefined ? null : endVal
-    );
-  };
-  toTimeline = (screen_delta: number, width: number) =>
-    Math.max(
-      0,
-      Math.min(this.duration, (screen_delta / width) * this.duration)
-    );
-  rangeClickScript = () => CollectionStackedTimeline.RangeScript;
-  rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
-
-  // for creating key anchors with key events
-  @action
-  keyEvents = (e: KeyboardEvent) => {
-    if (
-      !(e.target instanceof HTMLInputElement) &&
-      this.props.isSelected(true)
-    ) {
-      switch (e.key) {
-        case " ":
-          if (!CollectionStackedTimeline.SelectingRegion) {
-            this._markerStart = this._markerEnd = this.currentTime;
-            CollectionStackedTimeline.SelectingRegion = this;
-          } else {
-            CollectionStackedTimeline.createAnchor(
-              this.rootDoc,
-              this.dataDoc,
-              this.props.fieldKey,
-              this.props.startTag,
-              this.props.endTag,
-              this.currentTime
-            );
-            CollectionStackedTimeline.SelectingRegion = undefined;
-          }
-      }
+
+    get trimStart() {
+        return this.props.trimStart;
     }
-  };
-
-  getLinkData(l: Doc) {
-    let la1 = l.anchor1 as Doc;
-    let la2 = l.anchor2 as Doc;
-    const linkTime = NumCast(
-      la2[this.props.startTag],
-      NumCast(la1[this.props.startTag])
-    );
-    if (Doc.AreProtosEqual(la1, this.dataDoc)) {
-      la1 = l.anchor2 as Doc;
-      la2 = l.anchor1 as Doc;
+
+    get trimEnd() {
+        return this.props.trimEnd;
     }
-    return { la1, la2, linkTime };
-  }
-
-  // starting the drag event for anchor resizing
-  @action
-  onPointerDownTimeline = (e: React.PointerEvent): void => {
-    const rect = this._timeline?.getBoundingClientRect();
-    const clientX = e.clientX;
-    if (rect && this.props.isContentActive()) {
-      const wasPlaying = this.props.playing();
-      if (wasPlaying) this.props.Pause();
-      const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
-      setupMoveUpEvents(
-        this,
-        e,
-        action((e) => {
-          if (
-            !wasSelecting &&
-            CollectionStackedTimeline.SelectingRegion !== this
-          ) {
-            this._markerStart = this._markerEnd = this.toTimeline(
-              clientX - rect.x,
-              rect.width
-            );
-            CollectionStackedTimeline.SelectingRegion = this;
-          }
-          this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
-          return false;
-        }),
-        action((e, movement, isClick) => {
-          this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
-          if (this._markerEnd < this._markerStart) {
-            const tmp = this._markerStart;
-            this._markerStart = this._markerEnd;
-            this._markerEnd = tmp;
-          }
-          if (
-            !isClick &&
-            CollectionStackedTimeline.SelectingRegion === this &&
-            Math.abs(movement[0]) > 15 &&
-            !this.props.trimming
-          ) {
-            CollectionStackedTimeline.createAnchor(
-              this.rootDoc,
-              this.dataDoc,
-              this.props.fieldKey,
-              this.props.startTag,
-              this.props.endTag,
-              this._markerStart,
-              this._markerEnd
-            );
-          }
-          (!isClick || !wasSelecting) &&
-            (CollectionStackedTimeline.SelectingRegion = undefined);
-        }),
-        (e, doubleTap) => {
-          this.props.select(false);
-          e.shiftKey &&
-            CollectionStackedTimeline.createAnchor(
-              this.rootDoc,
-              this.dataDoc,
-              this.props.fieldKey,
-              this.props.startTag,
-              this.props.endTag,
-              this.currentTime
-            );
-          !wasPlaying && doubleTap && this.props.Play();
-        },
-        this.props.isSelected(true) || this.props.isContentActive(),
-        undefined,
-        () =>
-          !wasPlaying &&
-          this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
-      );
+
+    get duration() {
+        return this.props.duration;
+    }
+
+    @computed get currentTime() {
+        return NumCast(this.layoutDoc._currentTimecode);
+    }
+    @computed get selectionContainer() {
+        return CollectionStackedTimeline.SelectingRegion !== this ? null : (
+            <div
+                className="collectionStackedTimeline-selector"
+                style={{
+                    left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration) * 100}%`,
+                    width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration) * 100}%`,
+                }}
+            />
+        );
     }
-  };
-
-  @action
-  startTrimLeft = (e: React.PointerEvent): void => {
-    document.addEventListener("pointermove", this.dragTrimLeft);
-    document.addEventListener("pointerup", this.endTrimLeft);
-  };
-
-  @action
-  startTrimRight = (e: React.PointerEvent): void => {
-    document.addEventListener("pointermove", this.dragTrimRight);
-    document.addEventListener("pointerup", this.endTrimRight);
-  };
-
-  @action
-  dragTrimLeft = (e: MouseEvent) => {
-    const rect = this._timeline?.getBoundingClientRect();
-    if (rect && this.props.isContentActive()) {
-      this.trimStart = Math.min(
-        Math.max(
-          this.trimStart + (e.movementX / rect.width) * this.duration,
-          0
-        ),
-        this.trimEnd - this.minLength
-      );
+
+    constructor(props: any) {
+        super(props);
+        // onClick play scripts
+        CollectionStackedTimeline.RangeScript =
+            CollectionStackedTimeline.RangeScript ||
+            ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
+                self: Doc.name,
+                scriptContext: "any",
+                clientX: "number",
+            })!;
+        CollectionStackedTimeline.RangePlayScript =
+            CollectionStackedTimeline.RangePlayScript ||
+            ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
+                self: Doc.name,
+                scriptContext: "any",
+                clientX: "number",
+            })!;
     }
-  };
-
-  @action
-  dragTrimRight = (e: MouseEvent) => {
-    const rect = this._timeline?.getBoundingClientRect();
-    if (rect && this.props.isContentActive()) {
-      this.trimEnd = Math.max(
-        Math.min(
-          this.trimEnd + (e.movementX / rect.width) * this.duration,
-          this.duration
-        ),
-        this.trimStart + this.minLength
-      );
+
+    componentDidMount() {
+        document.addEventListener("keydown", this.keyEvents, true);
     }
-  };
-
-  endTrimLeft = () => {
-    document.removeEventListener("pointermove", this.dragTrimLeft);
-    document.removeEventListener("pointerup", this.endTrimLeft);
-  };
-
-  endTrimRight = () => {
-    document.removeEventListener("pointermove", this.dragTrimRight);
-    document.removeEventListener("pointerup", this.endTrimRight);
-  };
-
-  @undoBatch
-  @action
-  static createAnchor(
-    rootDoc: Doc,
-    dataDoc: Doc,
-    fieldKey: string,
-    startTag: string,
-    endTag: string,
-    anchorStartTime?: number,
-    anchorEndTime?: number
-  ) {
-    if (anchorStartTime === undefined) return rootDoc;
-    const anchor = Docs.Create.LabelDocument({
-      title: ComputedField.MakeFunction(
-        `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
-      ) as any,
-      useLinkSmallAnchor: true,
-      hideLinkButton: true,
-      annotationOn: rootDoc,
-      _timelineLabel: true,
-    });
-    Doc.GetProto(anchor)[startTag] = anchorStartTime;
-    Doc.GetProto(anchor)[endTag] = anchorEndTime;
-    if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
-      Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
-    } else {
-      dataDoc[fieldKey] = new List<Doc>([anchor]);
+    componentWillUnmount() {
+        document.removeEventListener("keydown", this.keyEvents, true);
+        if (CollectionStackedTimeline.SelectingRegion === this)
+            runInAction(
+                () => (CollectionStackedTimeline.SelectingRegion = undefined)
+            );
     }
-    return anchor;
-  }
-
-  @action
-  playOnClick = (anchorDoc: Doc, clientX: number) => {
-    const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
-    const endTime = this.anchorEnd(anchorDoc);
-    if (this.layoutDoc.autoPlayAnchors) {
-      if (this.props.playing()) this.props.Pause();
-      else this.props.playFrom(seekTimeInSeconds, endTime);
-    } else {
-      if (
-        seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
-        endTime > NumCast(this.layoutDoc._currentTimecode)
-      ) {
-        if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
-          this.props.Pause();
-        } else {
-          this.props.Play();
+
+    anchorStart = (anchor: Doc) =>
+        NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
+    anchorEnd = (anchor: Doc, val: any = null) => {
+        const endVal = NumCast(anchor[this.props.endTag], val);
+        return NumCast(
+            anchor._timecodeToHide,
+            endVal === undefined ? null : endVal
+        );
+    };
+    toTimeline = (screen_delta: number, width: number) => {
+        return Math.max(
+            this.trimStart,
+            Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration + this.trimStart))
+    }
+
+    rangeClickScript = () => CollectionStackedTimeline.RangeScript;
+    rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
+
+    // for creating key anchors with key events
+    @action
+    keyEvents = (e: KeyboardEvent) => {
+        if (
+            !(e.target instanceof HTMLInputElement) &&
+            this.props.isSelected(true)
+        ) {
+            switch (e.key) {
+                case " ":
+                    if (!CollectionStackedTimeline.SelectingRegion) {
+                        this._markerStart = this._markerEnd = this.currentTime;
+                        CollectionStackedTimeline.SelectingRegion = this;
+                    } else {
+                        CollectionStackedTimeline.createAnchor(
+                            this.rootDoc,
+                            this.dataDoc,
+                            this.props.fieldKey,
+                            this.props.startTag,
+                            this.props.endTag,
+                            this.currentTime
+                        );
+                        CollectionStackedTimeline.SelectingRegion = undefined;
+                    }
+            }
+        }
+    };
+
+    getLinkData(l: Doc) {
+        let la1 = l.anchor1 as Doc;
+        let la2 = l.anchor2 as Doc;
+        const linkTime = NumCast(
+            la2[this.props.startTag],
+            NumCast(la1[this.props.startTag])
+        );
+        if (Doc.AreProtosEqual(la1, this.dataDoc)) {
+            la1 = l.anchor2 as Doc;
+            la2 = l.anchor1 as Doc;
         }
-      } else {
-        this.props.playFrom(seekTimeInSeconds, endTime);
-      }
+        return { la1, la2, linkTime };
     }
-    return { select: true };
-  };
-
-  @action
-  clickAnchor = (anchorDoc: Doc, clientX: number) => {
-    if (anchorDoc.isLinkButton)
-      LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
-    const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
-    const endTime = this.anchorEnd(anchorDoc);
-    if (
-      seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 &&
-      endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4
-    ) {
-      if (this.props.playing()) this.props.Pause();
-      else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
-      else if (!this.layoutDoc.autoPlayAnchors) {
+
+    // starting the drag event for anchor resizing
+    @action
+    onPointerDownTimeline = (e: React.PointerEvent): void => {
         const rect = this._timeline?.getBoundingClientRect();
-        rect &&
-          this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
-      }
-    } else {
-      if (this.layoutDoc.autoPlayAnchors)
-        this.props.playFrom(seekTimeInSeconds, endTime);
-      else this.props.setTime(seekTimeInSeconds);
+        const clientX = e.clientX;
+        if (rect && this.props.isContentActive()) {
+            const wasPlaying = this.props.playing();
+            if (wasPlaying) this.props.Pause();
+            const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
+            setupMoveUpEvents(
+                this,
+                e,
+                action((e) => {
+                    if (
+                        !wasSelecting &&
+                        CollectionStackedTimeline.SelectingRegion !== this
+                    ) {
+                        this._markerStart = this._markerEnd = this.toTimeline(
+                            clientX - rect.x,
+                            rect.width
+                        );
+                        CollectionStackedTimeline.SelectingRegion = this;
+                    }
+                    this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+                    return false;
+                }),
+                action((e, movement, isClick) => {
+                    this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+                    if (this._markerEnd < this._markerStart) {
+                        const tmp = this._markerStart;
+                        this._markerStart = this._markerEnd;
+                        this._markerEnd = tmp;
+                    }
+                    if (
+                        !isClick &&
+                        CollectionStackedTimeline.SelectingRegion === this &&
+                        Math.abs(movement[0]) > 15 &&
+                        !this.props.trimming
+                    ) {
+                        CollectionStackedTimeline.createAnchor(
+                            this.rootDoc,
+                            this.dataDoc,
+                            this.props.fieldKey,
+                            this.props.startTag,
+                            this.props.endTag,
+                            this._markerStart,
+                            this._markerEnd
+                        );
+                    }
+                    (!isClick || !wasSelecting) &&
+                        (CollectionStackedTimeline.SelectingRegion = undefined);
+                }),
+                (e, doubleTap) => {
+                    this.props.select(false);
+                    e.shiftKey &&
+                        CollectionStackedTimeline.createAnchor(
+                            this.rootDoc,
+                            this.dataDoc,
+                            this.props.fieldKey,
+                            this.props.startTag,
+                            this.props.endTag,
+                            this.currentTime
+                        );
+                    !wasPlaying && doubleTap && this.props.Play();
+                },
+                this.props.isSelected(true) || this.props.isContentActive(),
+                undefined,
+                () => {
+                    !wasPlaying &&
+                        (this.props.trimming && this.duration ?
+                            this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
+                            :
+                            this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration + this.trimStart)
+                        )
+                }
+            );
+        }
+
+    };
+
+    @action
+    trimLeft = (e: React.PointerEvent): void => {
+        const rect = this._timeline?.getBoundingClientRect();
+        const clientX = e.movementX;
+        setupMoveUpEvents(
+            this,
+            e,
+            action((e, [], []) => {
+                if (rect && this.props.isContentActive()) {
+                    this.props.setStartTrim(Math.min(
+                        Math.max(
+                            this.trimStart + (e.movementX / rect.width) * this.duration,
+                            0
+                        ),
+                        this.trimEnd - this.minLength
+                    ));
+                }
+                return false;
+            }),
+            emptyFunction,
+            action((e, doubleTap) => {
+                if (doubleTap) {
+                    this.props.setStartTrim(0);
+                }
+            })
+        );
+    };
+
+    @action
+    trimRight = (e: React.PointerEvent): void => {
+        const rect = this._timeline?.getBoundingClientRect();
+        const clientX = e.movementX;
+        setupMoveUpEvents(
+            this,
+            e,
+            action((e, [], []) => {
+                if (rect && this.props.isContentActive()) {
+                    this.props.setEndTrim(Math.max(
+                        Math.min(
+                            this.trimEnd + (e.movementX / rect.width) * this.duration,
+                            this.duration
+                        ),
+                        this.trimStart + this.minLength
+                    ));
+                }
+                return false;
+            }),
+            emptyFunction,
+            action((e, doubleTap) => {
+                if (doubleTap) {
+                    this.props.setEndTrim(this.duration);
+                }
+            })
+        );
+    };
+
+    @undoBatch
+    @action
+    static createAnchor(
+        rootDoc: Doc,
+        dataDoc: Doc,
+        fieldKey: string,
+        startTag: string,
+        endTag: string,
+        anchorStartTime?: number,
+        anchorEndTime?: number
+    ) {
+        if (anchorStartTime === undefined) return rootDoc;
+        const anchor = Docs.Create.LabelDocument({
+            title: ComputedField.MakeFunction(
+                `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
+            ) as any,
+            useLinkSmallAnchor: true,
+            hideLinkButton: true,
+            annotationOn: rootDoc,
+            _timelineLabel: true,
+        });
+        Doc.GetProto(anchor)[startTag] = anchorStartTime;
+        Doc.GetProto(anchor)[endTag] = anchorEndTime;
+        if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+            Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
+        } else {
+            dataDoc[fieldKey] = new List<Doc>([anchor]);
+        }
+        return anchor;
     }
-    return { select: true };
-  };
-
-  // makes sure no anchors overlaps each other by setting the correct position and width
-  getLevel = (
-    m: Doc,
-    placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
-  ) => {
-    const timelineContentWidth = this.props.PanelWidth();
-    const x1 = this.anchorStart(m);
-    const x2 = this.anchorEnd(
-      m,
-      x1 + (10 / timelineContentWidth) * this.duration
-    );
-    let max = 0;
-    const overlappedLevels = new Set(
-      placed.map((p) => {
-        const y1 = p.anchorStartTime;
-        const y2 = p.anchorEndTime;
+
+    @action
+    playOnClick = (anchorDoc: Doc, clientX: number) => {
+        const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
+        const endTime = this.anchorEnd(anchorDoc);
+        if (this.layoutDoc.autoPlayAnchors) {
+            if (this.props.playing()) this.props.Pause();
+            else this.props.playFrom(seekTimeInSeconds, endTime);
+        } else {
+            if (
+                seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
+                endTime > NumCast(this.layoutDoc._currentTimecode)
+            ) {
+                if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
+                    this.props.Pause();
+                } else {
+                    this.props.Play();
+                }
+            } else {
+                this.props.playFrom(seekTimeInSeconds, endTime);
+            }
+        }
+        return { select: true };
+    };
+
+    @action
+    clickAnchor = (anchorDoc: Doc, clientX: number) => {
+        if (anchorDoc.isLinkButton)
+            LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+        const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
+        const endTime = this.anchorEnd(anchorDoc);
         if (
-          (x1 >= y1 && x1 <= y2) ||
-          (x2 >= y1 && x2 <= y2) ||
-          (y1 >= x1 && y1 <= x2) ||
-          (y2 >= x1 && y2 <= x2)
+            seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 &&
+            endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4
         ) {
-          max = Math.max(max, p.level);
-          return p.level;
+            if (this.props.playing()) this.props.Pause();
+            else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
+            else if (!this.layoutDoc.autoPlayAnchors) {
+                const rect = this._timeline?.getBoundingClientRect();
+                rect &&
+                    this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+            }
+        } else {
+            if (this.layoutDoc.autoPlayAnchors)
+                this.props.playFrom(seekTimeInSeconds, endTime);
+            else {
+                this.props.setTime(seekTimeInSeconds);
+            }
         }
-      })
-    );
-    let level = max + 1;
-    for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
-
-    placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
-    return level;
-  };
-
-  dictationHeightPercent = 50;
-  dictationHeight = () =>
-    (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
-  timelineContentHeight = () =>
-    (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
-  dictationScreenToLocalTransform = () =>
-    this.props
-      .ScreenToLocalTransform()
-      .translate(0, -this.timelineContentHeight());
-  @computed get renderDictation() {
-    const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
-    return !dictation ? null : (
-      <div
-        style={{
-          position: "absolute",
-          height: "100%",
-          top: this.timelineContentHeight(),
-          background: "tan",
-        }}
-      >
-        <DocumentView
-          {...OmitKeys(this.props, [
-            "NativeWidth",
-            "NativeHeight",
-            "setContentView",
-          ]).omit}
-          Document={dictation}
-          PanelHeight={this.dictationHeight}
-          isAnnotationOverlay={true}
-          isDocumentActive={returnFalse}
-          select={emptyFunction}
-          scaling={returnOne}
-          xMargin={25}
-          yMargin={10}
-          ScreenToLocalTransform={this.dictationScreenToLocalTransform}
-          whenChildContentsActiveChanged={emptyFunction}
-          removeDocument={returnFalse}
-          moveDocument={returnFalse}
-          addDocument={returnFalse}
-          CollectionView={undefined}
-          renderDepth={this.props.renderDepth + 1}
-        ></DocumentView>
-      </div>
-    );
-  }
-  @computed get renderAudioWaveform() {
-    return !this.props.mediaPath ? null : (
-      <div className="collectionStackedTimeline-waveform">
-        <AudioWaveform
-          duration={this.duration}
-          mediaPath={this.props.mediaPath}
-          dataDoc={this.dataDoc}
-          PanelHeight={this.timelineContentHeight}
-          trimming={this.props.trimming}
-        />
-      </div>
-    );
-  }
-
-  currentTimecode = () => this.currentTime;
-  render() {
-    const timelineContentWidth = this.props.PanelWidth();
-    const overlaps: {
-      anchorStartTime: number;
-      anchorEndTime: number;
-      level: number;
-    }[] = [];
-    const drawAnchors = this.childDocs.map((anchor) => ({
-      level: this.getLevel(anchor, overlaps),
-      anchor,
-    }));
-    const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
-    const isActive =
-      this.props.isContentActive() || this.props.isSelected(false);
-    return (
-      <div
-        className="collectionStackedTimeline"
-        ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
-        onClick={(e) => isActive && StopEvent(e)}
-        onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
-      >
-        {drawAnchors.map((d) => {
-          const start = this.anchorStart(d.anchor);
-          const end = this.anchorEnd(
-            d.anchor,
-            start + (10 / timelineContentWidth) * this.duration
-          );
-          const left = (start / this.duration) * timelineContentWidth;
-          const top = (d.level / maxLevel) * this.timelineContentHeight();
-          const timespan = end - start;
-          return this.props.Document.hideAnchors ? null : (
+        return { select: true };
+    };
+
+    // makes sure no anchors overlaps each other by setting the correct position and width
+    getLevel = (
+        m: Doc,
+        placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
+    ) => {
+        const timelineContentWidth = this.props.PanelWidth();
+        const x1 = this.anchorStart(m);
+        const x2 = this.anchorEnd(
+            m,
+            x1 + (10 / timelineContentWidth) * this.duration
+        );
+        let max = 0;
+        const overlappedLevels = new Set(
+            placed.map((p) => {
+                const y1 = p.anchorStartTime;
+                const y2 = p.anchorEndTime;
+                if (
+                    (x1 >= y1 && x1 <= y2) ||
+                    (x2 >= y1 && x2 <= y2) ||
+                    (y1 >= x1 && y1 <= x2) ||
+                    (y2 >= x1 && y2 <= x2)
+                ) {
+                    max = Math.max(max, p.level);
+                    return p.level;
+                }
+            })
+        );
+        let level = max + 1;
+        for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
+
+        placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
+        return level;
+    };
+
+    dictationHeightPercent = 50;
+    dictationHeight = () =>
+        (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+    timelineContentHeight = () =>
+        (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+    dictationScreenToLocalTransform = () =>
+        this.props
+            .ScreenToLocalTransform()
+            .translate(0, -this.timelineContentHeight());
+    @computed get renderDictation() {
+        const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
+        return !dictation ? null : (
             <div
-              className={"collectionStackedTimeline-marker-timeline"}
-              key={d.anchor[Id]}
-              style={{
-                left,
-                top,
-                width: `${(timespan / this.duration) * timelineContentWidth}px`,
-                height: `${this.timelineContentHeight() / maxLevel}px`,
-              }}
-              onClick={(e) => {
-                this.props.playFrom(start, this.anchorEnd(d.anchor));
-                e.stopPropagation();
-              }}
+                style={{
+                    position: "absolute",
+                    height: "100%",
+                    top: this.timelineContentHeight(),
+                    background: Colors.LIGHT_BLUE,
+                }}
             >
-              <StackedTimelineAnchor
-                {...this.props}
-                mark={d.anchor}
-                rangeClickScript={this.rangeClickScript}
-                rangePlayScript={this.rangePlayScript}
-                left={left}
-                top={top}
-                width={(timelineContentWidth * timespan) / this.duration}
-                height={this.timelineContentHeight() / maxLevel}
-                toTimeline={this.toTimeline}
-                layoutDoc={this.layoutDoc}
-                currentTimecode={this.currentTimecode}
-                _timeline={this._timeline}
-                stackedTimeline={this}
-              />
+                <DocumentView
+                    {...OmitKeys(this.props, [
+                        "NativeWidth",
+                        "NativeHeight",
+                        "setContentView",
+                    ]).omit}
+                    Document={dictation}
+                    PanelHeight={this.dictationHeight}
+                    isAnnotationOverlay={true}
+                    isDocumentActive={returnFalse}
+                    select={emptyFunction}
+                    scaling={returnOne}
+                    xMargin={25}
+                    yMargin={10}
+                    ScreenToLocalTransform={this.dictationScreenToLocalTransform}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    removeDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    addDocument={returnFalse}
+                    CollectionView={undefined}
+                    renderDepth={this.props.renderDepth + 1}
+                ></DocumentView>
             </div>
-          );
-        })}
-        {!this.props.trimming && this.selectionContainer}
-        {this.renderAudioWaveform}
-        {this.renderDictation}
-
-        <div
-          className="collectionStackedTimeline-current"
-          style={{
-            left: this.props.trimming
-              ? `${
-                  (this.currentTime /
-                    (NumCast(this.dataDoc.clipEnd) -
-                      NumCast(this.dataDoc.clipStart))) *
-                  100
-                }%`
-              : `${(this.currentTime / this.duration) * 100}%`,
-          }}
-        />
-
-        {this.props.trimming && (
-          <div>
-            <div
-              className="collectionStackedTimeline-trim-shade"
-              style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
-            ></div>
+        );
+    }
+    @computed get renderAudioWaveform() {
+        return !this.props.mediaPath ? null : (
+            <div className="collectionStackedTimeline-waveform">
+                <AudioWaveform
+                    duration={this.duration}
+                    mediaPath={this.props.mediaPath}
+                    layoutDoc={this.layoutDoc}
+                    PanelHeight={this.timelineContentHeight}
+                    trimming={this.props.trimming}
+                />
+            </div>
+        );
+    }
 
+    currentTimecode = () => this.currentTime;
+    render() {
+        const timelineContentWidth = this.props.PanelWidth();
+        const overlaps: {
+            anchorStartTime: number;
+            anchorEndTime: number;
+            level: number;
+        }[] = [];
+        const drawAnchors = this.childDocs.map((anchor) => ({
+            level: this.getLevel(anchor, overlaps),
+            anchor,
+        }));
+        const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
+        const isActive =
+            this.props.isContentActive() || this.props.isSelected(false);
+        return (
             <div
-              className="collectionStackedTimeline-trim-controls"
-              style={{
-                left: `${(this.trimStart / this.duration) * 100}%`,
-                width: `${
-                  ((this.trimEnd - this.trimStart) / this.duration) * 100
-                }%`,
-              }}
+                className="collectionStackedTimeline"
+                ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+                onClick={(e) => isActive && StopEvent(e)}
+                onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
             >
-              <div
-                className="collectionStackedTimeline-trim-handle"
-                onPointerDown={this.startTrimLeft}
-              ></div>
-              <div
-                className="collectionStackedTimeline-trim-handle"
-                onPointerDown={this.startTrimRight}
-              ></div>
+                {drawAnchors.map((d) => {
+
+                    const start = this.anchorStart(d.anchor);
+                    const end = this.anchorEnd(
+                        d.anchor,
+                        start + (10 / timelineContentWidth) * this.duration
+                    );
+                    const left = this.props.trimming ?
+                        (start / this.duration) * timelineContentWidth
+                        : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
+                    const top = (d.level / maxLevel) * this.timelineContentHeight();
+                    const timespan = end - start;
+                    const width = (timespan / this.props.trimDuration) * timelineContentWidth;
+                    const height = this.timelineContentHeight() / maxLevel
+                    return this.props.Document.hideAnchors ? null : (
+                        <div
+                            className={"collectionStackedTimeline-marker-timeline"}
+                            key={d.anchor[Id]}
+                            style={{
+                                left,
+                                top,
+                                width: `${width}px`,
+                                height: `${height}px`,
+                            }}
+                            onClick={(e) => {
+                                this.props.playFrom(start, this.anchorEnd(d.anchor));
+                                e.stopPropagation();
+                            }}
+                        >
+                            <StackedTimelineAnchor
+                                {...this.props}
+                                mark={d.anchor}
+                                rangeClickScript={this.rangeClickScript}
+                                rangePlayScript={this.rangePlayScript}
+                                left={left}
+                                top={top}
+                                width={width}
+                                height={height}
+                                toTimeline={this.toTimeline}
+                                layoutDoc={this.layoutDoc}
+                                currentTimecode={this.currentTimecode}
+                                _timeline={this._timeline}
+                                stackedTimeline={this}
+                                trimStart={this.trimStart}
+                                trimEnd={this.trimEnd}
+                            />
+                        </div>
+                    );
+                })}
+                {!this.props.trimming && this.selectionContainer}
+                {this.renderAudioWaveform}
+                {this.renderDictation}
+
+                <div
+                    className="collectionStackedTimeline-current"
+                    style={{
+                        left: this.props.trimming
+                            ? `${(this.currentTime / this.duration) * 100}%`
+                            : `${(this.currentTime - this.trimStart) / (this.trimEnd - this.trimStart) * 100}%`,
+                    }}
+                />
+
+                {this.props.trimming && (
+                    <>
+                        <div
+                            className="collectionStackedTimeline-trim-shade"
+                            style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
+                        ></div>
+
+                        <div
+                            className="collectionStackedTimeline-trim-controls"
+                            style={{
+                                left: `${(this.trimStart / this.duration) * 100}%`,
+                                width: `${((this.trimEnd - this.trimStart) / this.duration) * 100
+                                    }%`,
+                            }}
+                        >
+                            <div
+                                className="collectionStackedTimeline-trim-handle"
+                                onPointerDown={this.trimLeft}
+                            ></div>
+                            <div
+                                className="collectionStackedTimeline-trim-handle"
+                                onPointerDown={this.trimRight}
+                            ></div>
+                        </div>
+
+                        <div
+                            className="collectionStackedTimeline-trim-shade"
+                            style={{
+                                left: `${(this.trimEnd / this.duration) * 100}%`,
+                                width: `${((this.duration - this.trimEnd) / this.duration) * 100
+                                    }%`,
+                            }}
+                        ></div>
+                    </>
+                )}
             </div>
-
-            <div
-              className="collectionStackedTimeline-trim-shade"
-              style={{
-                left: `${(this.trimEnd / this.duration) * 100}%`,
-                width: `${
-                  ((this.duration - this.trimEnd) / this.duration) * 100
-                }%`,
-              }}
-            ></div>
-          </div>
-        )}
-      </div>
-    );
-  }
+        );
+    }
 }
 
 interface StackedTimelineAnchorProps {
-  mark: Doc;
-  rangeClickScript: () => ScriptField;
-  rangePlayScript: () => ScriptField;
-  left: number;
-  top: number;
-  width: number;
-  height: number;
-  toTimeline: (screen_delta: number, width: number) => number;
-  playLink: (linkDoc: Doc) => void;
-  setTime: (time: number) => void;
-  startTag: string;
-  endTag: string;
-  renderDepth: number;
-  layoutDoc: Doc;
-  ScreenToLocalTransform: () => Transform;
-  _timeline: HTMLDivElement | null;
-  focus: DocFocusFunc;
-  currentTimecode: () => number;
-  isSelected: (outsideReaction?: boolean) => boolean;
-  stackedTimeline: CollectionStackedTimeline;
+    mark: Doc;
+    rangeClickScript: () => ScriptField;
+    rangePlayScript: () => ScriptField;
+    left: number;
+    top: number;
+    width: number;
+    height: number;
+    toTimeline: (screen_delta: number, width: number) => number;
+    playLink: (linkDoc: Doc) => void;
+    setTime: (time: number) => void;
+    startTag: string;
+    endTag: string;
+    renderDepth: number;
+    layoutDoc: Doc;
+    ScreenToLocalTransform: () => Transform;
+    _timeline: HTMLDivElement | null;
+    focus: DocFocusFunc;
+    currentTimecode: () => number;
+    isSelected: (outsideReaction?: boolean) => boolean;
+    stackedTimeline: CollectionStackedTimeline;
+    trimStart: number;
+    trimEnd: number;
 }
 @observer
 class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
-  _lastTimecode: number;
-  _disposer: IReactionDisposer | undefined;
-  constructor(props: any) {
-    super(props);
-    this._lastTimecode = this.props.currentTimecode();
-  }
-  componentDidMount() {
-    this._disposer = reaction(
-      () => this.props.currentTimecode(),
-      (time) => {
-        const dictationDoc = Cast(
-          this.props.layoutDoc["data-dictation"],
-          Doc,
-          null
+    _lastTimecode: number;
+    _disposer: IReactionDisposer | undefined;
+    constructor(props: any) {
+        super(props);
+        this._lastTimecode = this.props.currentTimecode();
+    }
+    componentDidMount() {
+        this._disposer = reaction(
+            () => this.props.currentTimecode(),
+            (time) => {
+                const dictationDoc = Cast(
+                    this.props.layoutDoc["data-dictation"],
+                    Doc,
+                    null
+                );
+                const isDictation =
+                    dictationDoc &&
+                    DocListCast(this.props.mark.links).some(
+                        (link) =>
+                            Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc
+                    );
+                if (
+                    !LightboxView.LightboxDoc &&
+                    // bcz: when should links be followed?  we don't want to move away from the video to follow a link but we can open it in a sidebar/etc.  But we don't know that upfront.
+                    // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
+                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
+                    DocListCast(this.props.mark.links).length &&
+                    time > NumCast(this.props.mark[this.props.startTag]) &&
+                    time < NumCast(this.props.mark[this.props.endTag]) &&
+                    this._lastTimecode < NumCast(this.props.mark[this.props.startTag])
+                ) {
+                    LinkManager.FollowLink(
+                        undefined,
+                        this.props.mark,
+                        this.props as any as DocumentViewProps,
+                        false,
+                        true
+                    );
+                }
+                this._lastTimecode = time;
+            }
         );
-        const isDictation =
-          dictationDoc &&
-          DocListCast(this.props.mark.links).some(
-            (link) =>
-              Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc
-          );
-        if (
-          !LightboxView.LightboxDoc &&
-          // bcz: when should links be followed?  we don't want to move away from the video to follow a link but we can open it in a sidebar/etc.  But we don't know that upfront.
-          // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
-          /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
-          DocListCast(this.props.mark.links).length &&
-          time > NumCast(this.props.mark[this.props.startTag]) &&
-          time < NumCast(this.props.mark[this.props.endTag]) &&
-          this._lastTimecode < NumCast(this.props.mark[this.props.startTag])
-        ) {
-          LinkManager.FollowLink(
-            undefined,
-            this.props.mark,
-            (this.props as any) as DocumentViewProps,
-            false,
-            true
-          );
-        }
-        this._lastTimecode = time;
-      }
-    );
-  }
-  componentWillUnmount() {
-    this._disposer?.();
-  }
-  // starting the drag event for anchor resizing
-  onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
-    this.props._timeline?.setPointerCapture(e.pointerId);
-    const newTime = (e: PointerEvent) => {
-      const rect = (e.target as any).getBoundingClientRect();
-      return this.props.toTimeline(e.clientX - rect.x, rect.width);
-    };
-    const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
-      const timelineOnly =
-        Cast(anchor[this.props.startTag], "number", null) !== undefined;
-      if (timelineOnly)
-        Doc.SetInPlace(
-          anchor,
-          left ? this.props.startTag : this.props.endTag,
-          time,
-          true
+    }
+    componentWillUnmount() {
+        this._disposer?.();
+    }
+    // starting the drag event for anchor resizing
+    onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
+        this.props._timeline?.setPointerCapture(e.pointerId);
+        const newTime = (e: PointerEvent) => {
+            const rect = (e.target as any).getBoundingClientRect();
+            return this.props.toTimeline(e.clientX - rect.x, rect.width);
+        };
+        const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
+            const timelineOnly =
+                Cast(anchor[this.props.startTag], "number", null) !== undefined;
+            if (timelineOnly)
+                Doc.SetInPlace(
+                    anchor,
+                    left ? this.props.startTag : this.props.endTag,
+                    time,
+                    true
+                );
+            else
+                left
+                    ? (anchor._timecodeToShow = time)
+                    : (anchor._timecodeToHide = time);
+            return false;
+        };
+        setupMoveUpEvents(
+            this,
+            e,
+            (e) => changeAnchor(anchor, left, newTime(e)),
+            (e) => {
+                this.props.setTime(newTime(e));
+                this.props._timeline?.releasePointerCapture(e.pointerId);
+            },
+            emptyFunction
         );
-      else
-        left
-          ? (anchor._timecodeToShow = time)
-          : (anchor._timecodeToHide = time);
-      return false;
     };
-    setupMoveUpEvents(
-      this,
-      e,
-      (e) => changeAnchor(anchor, left, newTime(e)),
-      (e) => {
-        this.props.setTime(newTime(e));
-        this.props._timeline?.releasePointerCapture(e.pointerId);
-      },
-      emptyFunction
-    );
-  };
-  renderInner = computedFn(function (
-    this: StackedTimelineAnchor,
-    mark: Doc,
-    script: undefined | (() => ScriptField),
-    doublescript: undefined | (() => ScriptField),
-    x: number,
-    y: number,
-    width: number,
-    height: number
-  ) {
-    const anchor = observable({ view: undefined as any });
-    const focusFunc = (
-      doc: Doc,
-      willZoom?: boolean,
-      scale?: number,
-      afterFocus?: DocAfterFocusFunc,
-      docTransform?: Transform
-    ) => {
-      this.props.playLink(mark);
-      this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
-    };
-    return {
-      anchor,
-      view: (
-        <DocumentView
-          key="view"
-          {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-          ref={action((r: DocumentView | null) => (anchor.view = r))}
-          Document={mark}
-          DataDoc={undefined}
-          renderDepth={this.props.renderDepth + 1}
-          LayoutTemplate={undefined}
-          LayoutTemplateString={LabelBox.LayoutString("data")}
-          isDocumentActive={returnFalse}
-          PanelWidth={() => width}
-          PanelHeight={() => height}
-          ScreenToLocalTransform={() =>
-            this.props.ScreenToLocalTransform().translate(-x, -y)
-          }
-          focus={focusFunc}
-          rootSelected={returnFalse}
-          onClick={script}
-          onDoubleClick={
-            this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
-          }
-          ignoreAutoHeight={false}
-          hideResizeHandles={true}
-          bringToFront={emptyFunction}
-          scriptContext={this.props.stackedTimeline}
-        />
-      ),
-    };
-  });
-  render() {
-    const inner = this.renderInner(
-      this.props.mark,
-      this.props.rangeClickScript,
-      this.props.rangePlayScript,
-      this.props.left,
-      this.props.top,
-      this.props.width,
-      this.props.height
-    );
-    return (
-      <>
-        {inner.view}
-        {!inner.anchor.view ||
-        !SelectionManager.IsSelected(inner.anchor.view) ? null : (
-          <>
-            <div
-              key="left"
-              className="collectionStackedTimeline-left-resizer"
-              onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
-            />
-            <div
-              key="right"
-              className="collectionStackedTimeline-resizer"
-              onPointerDown={(e) =>
-                this.onAnchorDown(e, this.props.mark, false)
-              }
-            />
-          </>
-        )}
-      </>
-    );
-  }
+
+    @action
+    computeTitle = () => {
+        const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
+        const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+        return `#${formatTime(start)}-${formatTime(end)}`
+    }
+
+    renderInner = computedFn(function (
+        this: StackedTimelineAnchor,
+        mark: Doc,
+        script: undefined | (() => ScriptField),
+        doublescript: undefined | (() => ScriptField),
+        x: number,
+        y: number,
+        width: number,
+        height: number
+    ) {
+        const anchor = observable({ view: undefined as any });
+        const focusFunc = (
+            doc: Doc,
+            willZoom?: boolean,
+            scale?: number,
+            afterFocus?: DocAfterFocusFunc,
+            docTransform?: Transform
+        ) => {
+            this.props.playLink(mark);
+            this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
+        };
+        return {
+            anchor,
+            view: (
+                <DocumentView
+                    key="view"
+                    {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                    ref={action((r: DocumentView | null) => (anchor.view = r))}
+                    Document={mark}
+                    DataDoc={undefined}
+                    renderDepth={this.props.renderDepth + 1}
+                    LayoutTemplate={undefined}
+                    LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
+                    isDocumentActive={returnFalse}
+                    PanelWidth={() => width}
+                    PanelHeight={() => height}
+                    ScreenToLocalTransform={() =>
+                        this.props.ScreenToLocalTransform().translate(-x, -y)
+                    }
+                    focus={focusFunc}
+                    rootSelected={returnFalse}
+                    onClick={script}
+                    onDoubleClick={
+                        this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
+                    }
+                    ignoreAutoHeight={false}
+                    hideResizeHandles={true}
+                    bringToFront={emptyFunction}
+                    scriptContext={this.props.stackedTimeline}
+                />
+            ),
+        };
+    });
+
+    render() {
+        const inner = this.renderInner(
+            this.props.mark,
+            this.props.rangeClickScript,
+            this.props.rangePlayScript,
+            this.props.left,
+            this.props.top,
+            this.props.width,
+            this.props.height
+        );
+        return (
+            <>
+                {inner.view}
+                {!inner.anchor.view ||
+                    !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+                    <>
+                        <div
+                            key="left"
+                            className="collectionStackedTimeline-left-resizer"
+                            onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+                        />
+                        <div
+                            key="right"
+                            className="collectionStackedTimeline-resizer"
+                            onPointerDown={(e) =>
+                                this.onAnchorDown(e, this.props.mark, false)
+                            }
+                        />
+                    </>
+                )}
+            </>
+        );
+    }
 }
 Scripting.addGlobal(function formatToTime(time: number): any {
-  return formatTime(time);
+    return formatTime(time);
+});
+Scripting.addGlobal(function min(num1: number, num2: number): number {
+    return Math.min(num1, num2);
 });
+Scripting.addGlobal(function max(num1: number, num2: number): number {
+    return Math.max(num1, num2);
+});
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 0fb0dc70e..ac2b19fd6 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,3 +1,6 @@
+@import "../global/globalCssVariables.scss";
+
+
 .audiobox-container,
 .audiobox-container-interactive {
   width: 100%;
@@ -19,10 +22,11 @@
       height: 100%;
       align-items: center;
       display: inherit;
-      background: dimgray;
+      background: $medium-gray;
       left: 0px;
+      color: $dark-gray;
       &:hover {
-        color: white;
+        color: $black;
         cursor: pointer;
       }
     }
@@ -61,13 +65,13 @@
     position: relative;
     padding-right: 5px;
     display: flex;
-    background-color: red;
+    background-color: $medium-blue;
 
     .time {
       position: relative;
       height: 100%;
       width: 100%;
-      font-size: 20;
+      font-size: $large-header;
       text-align: center;
       top: 5;
     }
@@ -77,9 +81,11 @@
       margin-top: auto;
       margin-bottom: auto;
       width: 25px;
+      width: 25px;
       padding: 5px;
+      color: $dark-gray;
       &:hover {
-        background-color: crimson;
+        color: $black;
       }
     }
   }
@@ -89,16 +95,15 @@
     height: 100%;
     position: relative;
     display: flex;
-    padding-left: 2px;
-    background: black;
+    background: $dark-gray;
 
     .audiobox-dictation {
       position: absolute;
-      width: 32px;
+      width: 40px;
       height: 100%;
       align-items: center;
       display: inherit;
-      background: dimgray;
+      background: $medium-gray;
       left: 0px;
     }
 
@@ -110,22 +115,23 @@
       padding-right: 5px;
       display: flex;
       flex-direction: column;
+      justify-content: center;
 
-      .audiobox-playhead {
+      .audiobox-buttons {
         position: relative;
         margin-top: auto;
         margin-bottom: auto;
-        margin-right: 2px;
-        height: 25px;
+        width: 30px;
+        height: 30px;
         border-radius: 50%;
-        background-color: black;
-        color: white;
+        background-color: $dark-gray;
+        color: $white;
         display: flex;
         align-items: center;
         justify-content: center;
+        left: 5px;
         &:hover {
-          background-color: grey;
-          color: lightgrey;
+          background-color: $black;
         }
 
         svg {
@@ -141,30 +147,28 @@
         margin-top: auto;
         margin-bottom: auto;
         width: 25px;
-        padding: 2px;
         align-items: center;
         display: inherit;
-        background: dimgray;
+        background: $medium-gray;
       }
 
       .audiobox-timeline {
         position: absolute;
         width: 100%;
-        border: gray solid 1px;
-        border-radius: 3px;
         z-index: 1000;
         overflow: hidden;
+        border-right: 5px solid black;
       }
 
       .audioBox-total-time,
       .audioBox-current-time {
         position: absolute;
-        font-size: 8;
+        font-size: $small-text;
         top: 100%;
-        color: white;
+        color: $white;
       }
       .audioBox-current-time {
-        left: 30px;
+        left: 42px;
       }
 
       .audioBox-total-time {
@@ -189,12 +193,12 @@
     font-size: 3em;
   }
 
-  .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead,
+  .audiobox-container .audiobox-controls .audiobox-player .audiobox-buttons,
   .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
   .audiobox-container-interactive
     .audiobox-controls
     .audiobox-player
-    .audiobox-playhead {
+    .audiobox-buttons {
     width: 70px;
   }
 }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 99960e934..bcc8e042a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,12 +1,12 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import {
-  action,
-  computed,
-  IReactionDisposer,
-  observable,
-  reaction,
-  runInAction,
+    action,
+    computed,
+    IReactionDisposer,
+    observable,
+    reaction,
+    runInAction,
 } from "mobx";
 import { observer } from "mobx-react";
 import { DateField } from "../../../fields/DateField";
@@ -25,14 +25,17 @@ import { CollectionStackedTimeline } from "../collections/CollectionStackedTimel
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import {
-  ViewBoxAnnotatableComponent,
-  ViewBoxAnnotatableProps,
+    ViewBoxAnnotatableComponent,
+    ViewBoxAnnotatableProps,
 } from "../DocComponent";
 import "./AudioBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import { LinkDocPreview } from "./LinkDocPreview";
+import { faLessThan } from "@fortawesome/free-solid-svg-icons";
+import { Colors } from "../global/globalEnums";
+
 declare class MediaRecorder {
-  constructor(e: any); // whatever MediaRecorder has
+    constructor(e: any); // whatever MediaRecorder has
 }
 
 type AudioDocument = makeInterface<[typeof documentSchema]>;
@@ -40,649 +43,677 @@ const AudioDocument = makeInterface(documentSchema);
 
 @observer
 export class AudioBox extends ViewBoxAnnotatableComponent<
-  ViewBoxAnnotatableProps & FieldViewProps,
-  AudioDocument
+    ViewBoxAnnotatableProps & FieldViewProps,
+    AudioDocument
 >(AudioDocument) {
-  public static LayoutString(fieldKey: string) {
-    return FieldView.LayoutString(AudioBox, fieldKey);
-  }
-  public static Enabled = false;
-  static playheadWidth = 30; // width of playhead
-  static heightPercent = 80; // height of timeline in percent of height of audioBox.
-  static Instance: AudioBox;
-
-  _disposers: { [name: string]: IReactionDisposer } = {};
-  _ele: HTMLAudioElement | null = null;
-  _stackedTimeline = React.createRef<CollectionStackedTimeline>();
-  _recorder: any;
-  _recordStart = 0;
-  _pauseStart = 0;
-  _pauseEnd = 0;
-  _pausedTime = 0;
-  _stream: MediaStream | undefined;
-  _start: number = 0;
-  _play: any = null;
-
-  @observable static _scrubTime = 0;
-  @observable _markerEnd: number = 0;
-  @observable _position: number = 0;
-  @observable _waveHeight: Opt<number> = this.layoutDoc._height;
-  @observable _paused: boolean = false;
-  @observable _trimming: boolean = false;
-  @observable _trimBounds: { start: number; end: number } = {
-    start: this.dataDoc.clipStart ? this.dataDoc.clipStart : 0,
-    end: this.dataDoc.clipEnd
-      ? this.dataDoc.clipEnd
-      : this.duration
-      ? this.duration
-      : undefined,
-  };
-  @computed get mediaState():
-    | undefined
-    | "pendingRecording"
-    | "recording"
-    | "paused"
-    | "playing" {
-    return this.dataDoc.mediaState as
-      | undefined
-      | "pendingRecording"
-      | "recording"
-      | "paused"
-      | "playing";
-  }
-  set mediaState(value) {
-    this.dataDoc.mediaState = value;
-  }
-  public static SetScrubTime = action((timeInMillisFrom1970: number) => {
-    AudioBox._scrubTime = 0;
-    AudioBox._scrubTime = timeInMillisFrom1970;
-  });
-  @computed get recordingStart() {
-    return Cast(
-      this.dataDoc[this.props.fieldKey + "-recordingStart"],
-      DateField
-    )?.date.getTime();
-  }
-  @computed get duration() {
-    return NumCast(this.dataDoc[`${this.fieldKey}-duration`]);
-  }
-  @computed get anchorDocs() {
-    return DocListCast(this.dataDoc[this.annotationKey]);
-  }
-  @computed get links() {
-    return DocListCast(this.dataDoc.links);
-  }
-  @computed get pauseTime() {
-    return this._pauseEnd - this._pauseStart;
-  } // total time paused to update the correct time
-  @computed get heightPercent() {
-    return AudioBox.heightPercent;
-  }
-
-  constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) {
-    super(props);
-    AudioBox.Instance = this;
-
-    if (this.duration === undefined) {
-      runInAction(
-        () =>
-          (this.Document[this.fieldKey + "-duration"] = this.Document.duration)
-      );
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(AudioBox, fieldKey);
     }
-  }
-
-  getLinkData(l: Doc) {
-    let la1 = l.anchor1 as Doc;
-    let la2 = l.anchor2 as Doc;
-    const linkTime =
-      this._stackedTimeline.current?.anchorStart(la2) ||
-      this._stackedTimeline.current?.anchorStart(la1) ||
-      0;
-    if (Doc.AreProtosEqual(la1, this.dataDoc)) {
-      la1 = l.anchor2 as Doc;
-      la2 = l.anchor1 as Doc;
+    public static Enabled = false;
+    static playheadWidth = 40; // width of playhead
+    static heightPercent = 75; // height of timeline in percent of height of audioBox.
+    static Instance: AudioBox;
+
+    _disposers: { [name: string]: IReactionDisposer } = {};
+    _ele: HTMLAudioElement | null = null;
+    _stackedTimeline = React.createRef<CollectionStackedTimeline>();
+    _recorder: any;
+    _recordStart = 0;
+    _pauseStart = 0;
+    _pauseEnd = 0;
+    _pausedTime = 0;
+    _stream: MediaStream | undefined;
+    _start: number = 0;
+    _play: any = null;
+    _ended: boolean = false;
+
+    @observable static _scrubTime = 0;
+    @observable _markerEnd: number = 0;
+    @observable _position: number = 0;
+    @observable _waveHeight: Opt<number> = this.layoutDoc._height;
+    @observable _paused: boolean = false;
+    @observable _trimming: boolean = false;
+    @observable _trimStart: number = NumCast(this.layoutDoc.clipStart) ? NumCast(this.layoutDoc.clipStart) : 0;
+    @observable _trimEnd: number = NumCast(this.layoutDoc.clipEnd) ? NumCast(this.layoutDoc.clipEnd)
+        : this.duration;
+
+    @computed get mediaState():
+        | undefined
+        | "pendingRecording"
+        | "recording"
+        | "paused"
+        | "playing" {
+        return this.dataDoc.mediaState as
+            | undefined
+            | "pendingRecording"
+            | "recording"
+            | "paused"
+            | "playing";
     }
-    return { la1, la2, linkTime };
-  }
-
-  getAnchor = () => {
-    return (
-      CollectionStackedTimeline.createAnchor(
-        this.rootDoc,
-        this.dataDoc,
-        this.annotationKey,
-        "_timecodeToShow" /* audioStart */,
-        "_timecodeToHide" /* audioEnd */,
-        this._ele?.currentTime ||
-          Cast(this.props.Document._currentTimecode, "number", null) ||
-          (this.mediaState === "recording"
-            ? (Date.now() - (this.recordingStart || 0)) / 1000
-            : undefined)
-      ) || this.rootDoc
-    );
-  };
-
-  componentWillUnmount() {
-    Object.values(this._disposers).forEach((disposer) => disposer?.());
-    const ind = DocUtils.ActiveRecordings.indexOf(this);
-    ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
-  }
-
-  @action
-  componentDidMount() {
-    this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
-
-    this.mediaState = this.path ? "paused" : undefined;
-
-    this._disposers.triggerAudio = reaction(
-      () =>
-        !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1
-          ? NumCast(this.Document._triggerAudio, null)
-          : undefined,
-      (start) =>
-        start !== undefined &&
-        setTimeout(() => {
-          this.playFrom(start);
-          setTimeout(() => {
-            this.Document._currentTimecode = start;
-            this.Document._triggerAudio = undefined;
-          }, 10);
-        }), // wait for mainCont and try again to play
-      { fireImmediately: true }
-    );
-
-    this._disposers.audioStop = reaction(
-      () =>
-        this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo
-          ? Cast(this.Document._audioStop, "number", null)
-          : undefined,
-      (audioStop) =>
-        audioStop !== undefined &&
-        setTimeout(() => {
-          this.Pause();
-          setTimeout(() => (this.Document._audioStop = undefined), 10);
-        }), // wait for mainCont and try again to play
-      { fireImmediately: true }
-    );
-  }
-
-  // for updating the timecode
-  timecodeChanged = () => {
-    const htmlEle = this._ele;
-    if (this.mediaState !== "recording" && htmlEle) {
-      htmlEle.duration &&
-        htmlEle.duration !== Infinity &&
-        runInAction(
-          () => (this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration)
-        );
-      this.links
-        .map((l) => this.getLinkData(l))
-        .forEach(({ la1, la2, linkTime }) => {
-          if (
-            linkTime > NumCast(this.layoutDoc._currentTimecode) &&
-            linkTime < htmlEle.currentTime
-          ) {
-            Doc.linkFollowHighlight(la1);
-          }
-        });
-      this.layoutDoc._currentTimecode = htmlEle.currentTime;
+    set mediaState(value) {
+        this.dataDoc.mediaState = value;
     }
-  };
-
-  // pause play back
-  Pause = action(() => {
-    this._ele!.pause();
-    this.mediaState = "paused";
-  });
-
-  // play audio for documents created during recording
-  playFromTime = (absoluteTime: number) => {
-    this.recordingStart &&
-      this.playFrom((absoluteTime - this.recordingStart) / 1000);
-  };
-
-  // play back the audio from time
-  @action
-  playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
-    clearTimeout(this._play);
-    if (Number.isNaN(this._ele?.duration)) {
-      setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
-    } else if (this._ele && AudioBox.Enabled) {
-      if (seekTimeInSeconds < 0) {
-        if (seekTimeInSeconds > -1) {
-          setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
-        } else {
-          this.Pause();
+    public static SetScrubTime = action((timeInMillisFrom1970: number) => {
+        AudioBox._scrubTime = 0;
+        AudioBox._scrubTime = timeInMillisFrom1970;
+    });
+    @computed get recordingStart() {
+        return Cast(
+            this.dataDoc[this.props.fieldKey + "-recordingStart"],
+            DateField
+        )?.date.getTime();
+    }
+    @computed get duration() {
+        return NumCast(this.dataDoc[`${this.fieldKey}-duration`]);
+    }
+    @computed get trimDuration() {
+        return this._trimming && this._trimEnd ? this.duration : this._trimEnd - this._trimStart;
+    }
+    @computed get anchorDocs() {
+        return DocListCast(this.dataDoc[this.annotationKey]);
+    }
+    @computed get links() {
+        return DocListCast(this.dataDoc.links);
+    }
+    @computed get pauseTime() {
+        return this._pauseEnd - this._pauseStart;
+    } // total time paused to update the correct time
+    @computed get heightPercent() {
+        return AudioBox.heightPercent;
+    }
+
+    constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) {
+        super(props);
+        AudioBox.Instance = this;
+
+        if (this.duration === undefined) {
+            runInAction(
+                () =>
+                    (this.Document[this.fieldKey + "-duration"] = this.Document.duration)
+            );
         }
-      } else if (seekTimeInSeconds <= this._ele.duration) {
-        this._ele.currentTime = seekTimeInSeconds;
-        this._ele.play();
-        runInAction(() => (this.mediaState = "playing"));
-        if (endTime !== this.duration) {
-          this._play = setTimeout(
-            () => this.Pause(),
-            (endTime - seekTimeInSeconds) * 1000
-          ); // use setTimeout to play a specific duration
+    }
+
+    getLinkData(l: Doc) {
+        let la1 = l.anchor1 as Doc;
+        let la2 = l.anchor2 as Doc;
+        const linkTime =
+            this._stackedTimeline.current?.anchorStart(la2) ||
+            this._stackedTimeline.current?.anchorStart(la1) ||
+            0;
+        if (Doc.AreProtosEqual(la1, this.dataDoc)) {
+            la1 = l.anchor2 as Doc;
+            la2 = l.anchor1 as Doc;
         }
-      } else {
-        this.Pause();
-      }
+        return { la1, la2, linkTime };
     }
-  };
-
-  // update the recording time
-  updateRecordTime = () => {
-    if (this.mediaState === "recording") {
-      setTimeout(this.updateRecordTime, 30);
-      if (this._paused) {
-        this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
-      } else {
-        this.layoutDoc._currentTimecode =
-          (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
-      }
+
+    getAnchor = () => {
+        return (
+            CollectionStackedTimeline.createAnchor(
+                this.rootDoc,
+                this.dataDoc,
+                this.annotationKey,
+                "_timecodeToShow" /* audioStart */,
+                "_timecodeToHide" /* audioEnd */,
+                this._ele?.currentTime ||
+                Cast(this.props.Document._currentTimecode, "number", null) ||
+                (this.mediaState === "recording"
+                    ? (Date.now() - (this.recordingStart || 0)) / 1000
+                    : undefined)
+            ) || this.rootDoc
+        );
+    };
+
+    componentWillUnmount() {
+        Object.values(this._disposers).forEach((disposer) => disposer?.());
+        const ind = DocUtils.ActiveRecordings.indexOf(this);
+        ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
     }
-  };
-
-  // starts recording
-  recordAudioAnnotation = async () => {
-    this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
-    this._recorder = new MediaRecorder(this._stream);
-    this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(
-      new Date()
-    );
-    DocUtils.ActiveRecordings.push(this);
-    this._recorder.ondataavailable = async (e: any) => {
-      const [{ result }] = await Networking.UploadFilesToServer(e.data);
-      if (!(result instanceof Error)) {
-        this.props.Document[this.props.fieldKey] = new AudioField(
-          Utils.prepend(result.accessPaths.agnostic.client)
+
+    @action
+    componentDidMount() {
+        this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+
+        this.mediaState = this.path ? "paused" : undefined;
+
+        this.layoutDoc.clipStart = this.layoutDoc.clipStart ? this.layoutDoc.clipStart : 0;
+        this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? this.layoutDoc.clipEnd : this.duration ? this.duration : undefined;
+
+        this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart));
+        this.path && this.timecodeChanged();
+
+        this._disposers.triggerAudio = reaction(
+            () =>
+                !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1
+                    ? NumCast(this.Document._triggerAudio, null)
+                    : undefined,
+            (start) =>
+                start !== undefined &&
+                setTimeout(() => {
+                    this.playFrom(start);
+                    setTimeout(() => {
+                        this.Document._currentTimecode = start;
+                        this.Document._triggerAudio = undefined;
+                    }, 10);
+                }), // wait for mainCont and try again to play
+            { fireImmediately: true }
         );
-      }
+
+        this._disposers.audioStop = reaction(
+            () =>
+                this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo
+                    ? Cast(this.Document._audioStop, "number", null)
+                    : undefined,
+            (audioStop) =>
+                audioStop !== undefined &&
+                setTimeout(() => {
+                    this.Pause();
+                    setTimeout(() => (this.Document._audioStop = undefined), 10);
+                }), // wait for mainCont and try again to play
+            { fireImmediately: true }
+        );
+    }
+
+    // for updating the timecode
+    @action
+    timecodeChanged = () => {
+        const htmlEle = this._ele;
+        if (this.mediaState !== "recording" && htmlEle) {
+            htmlEle.duration &&
+                htmlEle.duration !== Infinity &&
+                runInAction(
+                    () => (this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration)
+                );
+            this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? Math.min(this.duration, NumCast(this.layoutDoc.clipEnd)) : this.duration;
+            this._trimEnd = this._trimEnd ? Math.min(this.duration, this._trimEnd) : this.duration;
+            this.links
+                .map((l) => this.getLinkData(l))
+                .forEach(({ la1, la2, linkTime }) => {
+                    if (
+                        linkTime > NumCast(this.layoutDoc._currentTimecode) &&
+                        linkTime < htmlEle.currentTime
+                    ) {
+                        Doc.linkFollowHighlight(la1);
+                    }
+                });
+            this.layoutDoc._currentTimecode = htmlEle.currentTime;
+
+        }
     };
-    this._recordStart = new Date().getTime();
-    runInAction(() => (this.mediaState = "recording"));
-    setTimeout(this.updateRecordTime, 0);
-    this._recorder.start();
-    setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
-  };
-
-  // context menu
-  specificContextMenu = (e: React.MouseEvent): void => {
-    const funcs: ContextMenuProps[] = [];
-    funcs.push({
-      description:
-        (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors",
-      event: () => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
-      icon: "expand-arrows-alt",
-    });
-    funcs.push({
-      description:
-        (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") +
-        " play when link is selected",
-      event: () =>
-        (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc
-          .dontAutoPlayFollowedLinks),
-      icon: "expand-arrows-alt",
-    });
-    funcs.push({
-      description:
-        (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") +
-        " anchors onClick",
-      event: () =>
-        (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
-      icon: "expand-arrows-alt",
+
+    // pause play back
+    Pause = action(() => {
+        this._ele!.pause();
+        this.mediaState = "paused";
     });
-    ContextMenu.Instance?.addItem({
-      description: "Options...",
-      subitems: funcs,
-      icon: "asterisk",
+
+    // play audio for documents created during recording
+    playFromTime = (absoluteTime: number) => {
+        this.recordingStart &&
+            this.playFrom((absoluteTime - this.recordingStart) / 1000);
+    };
+
+    // play back the audio from time
+    @action
+    playFrom = (seekTimeInSeconds: number, endTime: number = this._trimEnd, fullPlay: boolean = false) => {
+        clearTimeout(this._play);
+        if (Number.isNaN(this._ele?.duration)) {
+            setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
+        } else if (this._ele && AudioBox.Enabled) {
+            if (seekTimeInSeconds < 0) {
+                if (seekTimeInSeconds > -1) {
+                    setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
+                } else {
+                    this.Pause();
+                }
+            } else if (this._trimStart <= endTime && seekTimeInSeconds <= this._trimEnd) {
+                const start = Math.max(this._trimStart, seekTimeInSeconds);
+                const end = Math.min(this._trimEnd, endTime);
+                this._ele.currentTime = start;
+                this._ele.play();
+                runInAction(() => (this.mediaState = "playing"));
+                if (endTime !== this.duration) {
+                    this._play = setTimeout(
+                        () => {
+                            this._ended = fullPlay ? true : this._ended;
+                            this.Pause()
+                        },
+                        (end - start) * 1000
+                    ); // use setTimeout to play a specific duration
+                }
+            } else {
+                this.Pause();
+            }
+        }
+    };
+
+    // update the recording time
+    updateRecordTime = () => {
+        if (this.mediaState === "recording") {
+            setTimeout(this.updateRecordTime, 30);
+            if (this._paused) {
+                this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
+            } else {
+                this.layoutDoc._currentTimecode =
+                    (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+            }
+        }
+    };
+
+    // starts recording
+    recordAudioAnnotation = async () => {
+        this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+        this._recorder = new MediaRecorder(this._stream);
+        this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(
+            new Date()
+        );
+        DocUtils.ActiveRecordings.push(this);
+        this._recorder.ondataavailable = async (e: any) => {
+            const [{ result }] = await Networking.UploadFilesToServer(e.data);
+            if (!(result instanceof Error)) {
+                this.props.Document[this.props.fieldKey] = new AudioField(
+                    Utils.prepend(result.accessPaths.agnostic.client)
+                );
+            }
+        };
+        this._recordStart = new Date().getTime();
+        runInAction(() => (this.mediaState = "recording"));
+        setTimeout(this.updateRecordTime, 0);
+        this._recorder.start();
+        setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
+    };
+
+    // context menu
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({
+            description:
+                (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors",
+            event: () => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+            icon: "expand-arrows-alt",
+        });
+        funcs.push({
+            description:
+                (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") +
+                " play when link is selected",
+            event: () =>
+            (this.layoutDoc.dontAutoPlayFollowedLinks =
+                !this.layoutDoc.dontAutoPlayFollowedLinks),
+            icon: "expand-arrows-alt",
+        });
+        funcs.push({
+            description:
+                (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") +
+                " anchors onClick",
+            event: () =>
+                (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+            icon: "expand-arrows-alt",
+        });
+        ContextMenu.Instance?.addItem({
+            description: "Options...",
+            subitems: funcs,
+            icon: "asterisk",
+        });
+    };
+
+    // stops the recording
+    stopRecording = action(() => {
+        this._recorder.stop();
+        this._recorder = undefined;
+        this.dataDoc[this.fieldKey + "-duration"] =
+            (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+        this.mediaState = "paused";
+        this._trimEnd = this.duration;
+        this.layoutDoc.clipStart = 0;
+        this.layoutDoc.clipEnd = this.duration;
+        this._stream?.getAudioTracks()[0].stop();
+        const ind = DocUtils.ActiveRecordings.indexOf(this);
+        ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
     });
-  };
-
-  // stops the recording
-  stopRecording = action(() => {
-    this._recorder.stop();
-    this._recorder = undefined;
-    this.dataDoc[this.fieldKey + "-duration"] =
-      (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
-    this.mediaState = "paused";
-    this._trimBounds.end = this.duration;
-    this.dataDoc.clipStart = 0;
-    this.dataDoc.clipEnd = this.duration;
-    this._stream?.getAudioTracks()[0].stop();
-    const ind = DocUtils.ActiveRecordings.indexOf(this);
-    ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
-  });
-
-  // button for starting and stopping the recording
-  recordClick = (e: React.MouseEvent) => {
-    if (e.button === 0 && !e.ctrlKey) {
-      this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
-      e.stopPropagation();
+
+    // button for starting and stopping the recording
+    recordClick = (e: React.MouseEvent) => {
+        if (e.button === 0 && !e.ctrlKey) {
+            this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
+            e.stopPropagation();
+        }
+    };
+
+    // for play button
+    Play = (e?: any) => {
+        let start;
+        if (this._ended || this._ele!.currentTime == this.duration) {
+            start = this._trimStart;
+            this._ended = false;
+        }
+        else {
+            start = this._ele!.currentTime;
+        }
+
+        this.playFrom(start, this._trimEnd, true);
+        e?.stopPropagation?.();
+    };
+
+    // creates a text document for dictation
+    onFile = (e: any) => {
+        const newDoc = CurrentUserUtils.GetNewTextDoc(
+            "",
+            NumCast(this.props.Document.x),
+            NumCast(this.props.Document.y) +
+            NumCast(this.props.Document._height) +
+            10,
+            NumCast(this.props.Document._width),
+            2 * NumCast(this.props.Document._height)
+        );
+        Doc.GetProto(newDoc).recordingSource = this.dataDoc;
+        Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
+            `self.recordingSource["${this.props.fieldKey}-recordingStart"]`
+        );
+        Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
+            "self.recordingSource.mediaState"
+        );
+        this.props.addDocument?.(newDoc);
+        e.stopPropagation();
+    };
+
+    // ref for updating time
+    setRef = (e: HTMLAudioElement | null) => {
+        e?.addEventListener("timeupdate", this.timecodeChanged);
+        e?.addEventListener("ended", this.Pause);
+        this._ele = e;
+    };
+
+    // returns the path of the audio file
+    @computed get path() {
+        const field = Cast(this.props.Document[this.props.fieldKey], AudioField);
+        const path = field instanceof AudioField ? field.url.href : "";
+        return path === nullAudio ? "" : path;
     }
-  };
-
-  // for play button
-  Play = (e?: any) => {
-    if (this._trimming) {
-      let end = this._trimBounds.end;
-      let start =
-        this._ele!.currentTime > end ||
-        this._ele!.currentTime < this._trimBounds.start
-          ? -1
-          : this._trimBounds.start;
-      this.playFrom(this._ele!.paused ? this._ele!.currentTime : start, end);
-    } else {
-      this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1);
+
+    // returns the html audio element
+    @computed get audio() {
+        return (
+            <audio
+                ref={this.setRef}
+                className={`audiobox-control${this.isContentActive() ? "-interactive" : ""
+                    }`}
+            >
+                <source src={this.path} type="audio/mpeg" />
+                Not supported.
+            </audio>
+        );
     }
-    e?.stopPropagation?.();
-  };
-
-  // creates a text document for dictation
-  onFile = (e: any) => {
-    const newDoc = CurrentUserUtils.GetNewTextDoc(
-      "",
-      NumCast(this.props.Document.x),
-      NumCast(this.props.Document.y) +
-        NumCast(this.props.Document._height) +
-        10,
-      NumCast(this.props.Document._width),
-      2 * NumCast(this.props.Document._height)
-    );
-    Doc.GetProto(newDoc).recordingSource = this.dataDoc;
-    Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
-      `self.recordingSource["${this.props.fieldKey}-recordingStart"]`
-    );
-    Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
-      "self.recordingSource.mediaState"
-    );
-    this.props.addDocument?.(newDoc);
-    e.stopPropagation();
-  };
-
-  // ref for updating time
-  setRef = (e: HTMLAudioElement | null) => {
-    e?.addEventListener("timeupdate", this.timecodeChanged);
-    e?.addEventListener("ended", this.Pause);
-    this._ele = e;
-  };
-
-  // returns the path of the audio file
-  @computed get path() {
-    const field = Cast(this.props.Document[this.props.fieldKey], AudioField);
-    const path = field instanceof AudioField ? field.url.href : "";
-    return path === nullAudio ? "" : path;
-  }
-
-  // returns the html audio element
-  @computed get audio() {
-    return (
-      <audio
-        ref={this.setRef}
-        className={`audiobox-control${
-          this.isContentActive() ? "-interactive" : ""
-        }`}
-      >
-        <source src={this.path} type="audio/mpeg" />
-        Not supported.
-      </audio>
-    );
-  }
-
-  // pause the time during recording phase
-  @action
-  recordPause = (e: React.MouseEvent) => {
-    this._pauseStart = new Date().getTime();
-    this._paused = true;
-    this._recorder.pause();
-    e.stopPropagation();
-  };
-
-  // continue the recording
-  @action
-  recordPlay = (e: React.MouseEvent) => {
-    this._pauseEnd = new Date().getTime();
-    this._paused = false;
-    this._recorder.resume();
-    e.stopPropagation();
-  };
-
-  playing = () => this.mediaState === "playing";
-  playLink = (link: Doc) => {
-    const stack = this._stackedTimeline.current;
-    if (link.annotationOn === this.rootDoc) {
-      if (!this.layoutDoc.dontAutoPlayFollowedLinks)
-        this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
-      else
-        this._ele!.currentTime = this.layoutDoc._currentTimecode =
-          stack?.anchorStart(link) || 0;
-    } else {
-      this.links
-        .filter((l) => l.anchor1 === link || l.anchor2 === link)
-        .forEach((l) => {
-          const { la1, la2 } = this.getLinkData(l);
-          const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
-          const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
-          if (startTime !== undefined) {
+
+    // pause the time during recording phase
+    @action
+    recordPause = (e: React.MouseEvent) => {
+        this._pauseStart = new Date().getTime();
+        this._paused = true;
+        this._recorder.pause();
+        e.stopPropagation();
+    };
+
+    // continue the recording
+    @action
+    recordPlay = (e: React.MouseEvent) => {
+        this._pauseEnd = new Date().getTime();
+        this._paused = false;
+        this._recorder.resume();
+        e.stopPropagation();
+    };
+
+    playing = () => this.mediaState === "playing";
+    playLink = (link: Doc) => {
+        const stack = this._stackedTimeline.current;
+        if (link.annotationOn === this.rootDoc) {
             if (!this.layoutDoc.dontAutoPlayFollowedLinks)
-              endTime
-                ? this.playFrom(startTime, endTime)
-                : this.playFrom(startTime);
+                this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
             else
-              this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
-          }
-        });
+                this._ele!.currentTime = this.layoutDoc._currentTimecode =
+                    stack?.anchorStart(link) || 0;
+        } else {
+            this.links
+                .filter((l) => l.anchor1 === link || l.anchor2 === link)
+                .forEach((l) => {
+                    const { la1, la2 } = this.getLinkData(l);
+                    const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
+                    const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
+                    if (startTime !== undefined) {
+                        if (!this.layoutDoc.dontAutoPlayFollowedLinks)
+                            endTime
+                                ? this.playFrom(startTime, endTime)
+                                : this.playFrom(startTime);
+                        else
+                            this._ele!.currentTime = this.layoutDoc._currentTimecode =
+                                startTime;
+                    }
+                });
+        }
+    };
+
+    // shows trim controls
+    @action
+    startTrim = () => {
+        if (!this.duration) {
+            this.timecodeChanged();
+        }
+        if (this.mediaState === "playing") {
+            this.Pause();
+        }
+        this._trimming = true;
+    };
+
+    // hides trim controls and displays new clip
+    @action
+    finishTrim = () => {
+        if (this.mediaState === "playing") {
+            this.Pause();
+        }
+        this.layoutDoc.clipStart = this._trimStart;
+        this.layoutDoc.clipEnd = this._trimEnd;
+        this._trimming = false;
+        this.setAnchorTime(Math.max(Math.min(this._trimEnd, this._ele!.currentTime), this._trimStart));
+    };
+
+    @action
+    setStartTrim = (newStart: number) => {
+        this._trimStart = newStart;
     }
-  };
 
-  // shows trim controls
-  @action
-  startTrim = () => {
-    if (this.mediaState === "playing") {
-      this.Pause();
+    @action
+    setEndTrim = (newEnd: number) => {
+        this._trimEnd = newEnd;
     }
-    this._trimming = true;
-  };
-
-  // hides trim controls and displays new clip
-  @action
-  finishTrim = () => {
-    if (this.mediaState === "playing") {
-      this.Pause();
+
+    isActiveChild = () => this._isAnyChildContentActive;
+    timelineWhenChildContentsActiveChanged = (isActive: boolean) =>
+        this.props.whenChildContentsActiveChanged(
+            runInAction(() => (this._isAnyChildContentActive = isActive))
+        );
+    timelineScreenToLocal = () =>
+        this.props
+            .ScreenToLocalTransform()
+            .translate(
+                -AudioBox.playheadWidth,
+                (-(100 - this.heightPercent) / 200) * this.props.PanelHeight()
+            );
+    setAnchorTime = (time: number) => {
+        (this._ele!.currentTime = this.layoutDoc._currentTimecode = time);
     }
-    this.dataDoc.clipStart = this._trimBounds.start;
-    this.dataDoc.clipEnd = this._trimBounds.end;
-    this._trimming = false;
-  };
-
-  isActiveChild = () => this._isAnyChildContentActive;
-  timelineWhenChildContentsActiveChanged = (isActive: boolean) =>
-    this.props.whenChildContentsActiveChanged(
-      runInAction(() => (this._isAnyChildContentActive = isActive))
-    );
-  timelineScreenToLocal = () =>
-    this.props
-      .ScreenToLocalTransform()
-      .translate(
-        -AudioBox.playheadWidth,
-        (-(100 - this.heightPercent) / 200) * this.props.PanelHeight()
-      );
-  setAnchorTime = (time: number) =>
-    (this._ele!.currentTime = this.layoutDoc._currentTimecode = time);
-  timelineHeight = () =>
-    (((this.props.PanelHeight() * this.heightPercent) / 100) *
-      this.heightPercent) /
-    100; // panelHeight * heightPercent is player height.  * heightPercent is timeline height (as per css inline)
-  timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
-  @computed get renderTimeline() {
-    return (
-      <CollectionStackedTimeline
-        ref={this._stackedTimeline}
-        {...this.props}
-        fieldKey={this.annotationKey}
-        dictationKey={this.fieldKey + "-dictation"}
-        mediaPath={this.path}
-        renderDepth={this.props.renderDepth + 1}
-        startTag={"_timecodeToShow" /* audioStart */}
-        endTag={"_timecodeToHide" /* audioEnd */}
-        focus={DocUtils.DefaultFocus}
-        bringToFront={emptyFunction}
-        CollectionView={undefined}
-        duration={this.duration}
-        playFrom={this.playFrom}
-        setTime={this.setAnchorTime}
-        playing={this.playing}
-        whenChildContentsActiveChanged={
-          this.timelineWhenChildContentsActiveChanged
-        }
-        removeDocument={this.removeDocument}
-        ScreenToLocalTransform={this.timelineScreenToLocal}
-        Play={this.Play}
-        Pause={this.Pause}
-        isContentActive={this.isContentActive}
-        playLink={this.playLink}
-        PanelWidth={this.timelineWidth}
-        PanelHeight={this.timelineHeight}
-        trimming={this._trimming}
-        trimBounds={this._trimBounds}
-      />
-    );
-  }
-
-  render() {
-    const interactive =
-      SnappingManager.GetIsDragging() || this.isContentActive()
-        ? "-interactive"
-        : "";
-    return (
-      <div
-        className="audiobox-container"
-        onContextMenu={this.specificContextMenu}
-        onClick={
-          !this.path && !this._recorder ? this.recordAudioAnnotation : undefined
-        }
-        style={{
-          pointerEvents:
-            this.props.layerProvider?.(this.layoutDoc) === false
-              ? "none"
-              : undefined,
-        }}
-      >
-        {!this.path ? (
-          <div className="audiobox-buttons">
-            <div className="audiobox-dictation" onClick={this.onFile}>
-              <FontAwesomeIcon
-                style={{
-                  width: "30px",
-                  background: !this.layoutDoc.dontAutoPlayFollowedLinks
-                    ? "yellow"
-                    : "rgba(0,0,0,0)",
-                }}
-                icon="file-alt"
-                size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
-              />
-            </div>
-            {this.mediaState === "recording" || this.mediaState === "paused" ? (
-              <div className="recording" onClick={(e) => e.stopPropagation()}>
-                <div className="buttons" onClick={this.recordClick}>
-                  <FontAwesomeIcon
-                    icon={"stop"}
-                    size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
-                  />
-                </div>
-                <div
-                  className="buttons"
-                  onClick={this._paused ? this.recordPlay : this.recordPause}
-                >
-                  <FontAwesomeIcon
-                    icon={this._paused ? "play" : "pause"}
-                    size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
-                  />
-                </div>
-                <div className="time">
-                  {formatTime(
-                    Math.round(NumCast(this.layoutDoc._currentTimecode))
-                  )}
-                </div>
-              </div>
-            ) : (
-              <button
-                className={`audiobox-record${interactive}`}
-                style={{ backgroundColor: "black" }}
-              >
-                RECORD
-              </button>
-            )}
-          </div>
-        ) : (
-          <div
-            className="audiobox-controls"
-            style={{
-              pointerEvents:
-                this._isAnyChildContentActive || this.isContentActive()
-                  ? "all"
-                  : "none",
-            }}
-          >
-            <div className="audiobox-dictation" />
+
+    timelineHeight = () =>
+        (((this.props.PanelHeight() * this.heightPercent) / 100) *
+            this.heightPercent) /
+        100; // panelHeight * heightPercent is player height.  * heightPercent is timeline height (as per css inline)
+    timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
+    @computed get renderTimeline() {
+        return (
+            <CollectionStackedTimeline
+                ref={this._stackedTimeline}
+                {...this.props}
+                fieldKey={this.annotationKey}
+                dictationKey={this.fieldKey + "-dictation"}
+                mediaPath={this.path}
+                renderDepth={this.props.renderDepth + 1}
+                startTag={"_timecodeToShow" /* audioStart */}
+                endTag={"_timecodeToHide" /* audioEnd */}
+                focus={DocUtils.DefaultFocus}
+                bringToFront={emptyFunction}
+                CollectionView={undefined}
+                duration={this.duration}
+                playFrom={this.playFrom}
+                setTime={this.setAnchorTime}
+                playing={this.playing}
+                whenChildContentsActiveChanged={
+                    this.timelineWhenChildContentsActiveChanged
+                }
+                removeDocument={this.removeDocument}
+                ScreenToLocalTransform={this.timelineScreenToLocal}
+                Play={this.Play}
+                Pause={this.Pause}
+                isContentActive={this.isContentActive}
+                playLink={this.playLink}
+                PanelWidth={this.timelineWidth}
+                PanelHeight={this.timelineHeight}
+                trimming={this._trimming}
+                trimStart={this._trimStart}
+                trimEnd={this._trimEnd}
+                trimDuration={this.trimDuration}
+                setStartTrim={this.setStartTrim}
+                setEndTrim={this.setEndTrim}
+            />
+        );
+    }
+
+    render() {
+        const interactive =
+            SnappingManager.GetIsDragging() || this.isContentActive()
+                ? "-interactive"
+                : "";
+        return (
             <div
-              className="audiobox-player"
-              style={{ height: `${AudioBox.heightPercent}%` }}
-            >
-              <div
-                className="audiobox-playhead"
-                style={{
-                  width: AudioBox.playheadWidth,
-                  height: AudioBox.playheadWidth,
-                }}
-                title={this.mediaState === "paused" ? "play" : "pause"}
-                onClick={this.Play}
-              >
-                {" "}
-                <FontAwesomeIcon
-                  icon={this.mediaState === "paused" ? "play" : "pause"}
-                  size={"1x"}
-                />
-              </div>
-              <div
-                className="audiobox-playhead"
-                style={{
-                  width: AudioBox.playheadWidth,
-                  height: AudioBox.playheadWidth,
-                }}
-                title={this._trimming ? "finish" : "trim"}
-                onClick={this._trimming ? this.finishTrim : this.startTrim}
-              >
-                <FontAwesomeIcon
-                  icon={this._trimming ? "check" : "cut"}
-                  size={"1x"}
-                />
-              </div>
-              <div
-                className="audiobox-timeline"
+                className="audiobox-container"
+                onContextMenu={this.specificContextMenu}
+                onClick={
+                    !this.path && !this._recorder ? this.recordAudioAnnotation : undefined
+                }
                 style={{
-                  top: 0,
-                  height: `100%`,
-                  left: AudioBox.playheadWidth,
-                  width: `calc(100% - ${AudioBox.playheadWidth}px)`,
-                  background: "white",
+                    pointerEvents:
+                        this.props.layerProvider?.(this.layoutDoc) === false
+                            ? "none"
+                            : undefined,
                 }}
-              >
-                {this.renderTimeline}
-              </div>
-              {this.audio}
-              <div className="audioBox-current-time">
-                {formatTime(
-                  Math.round(NumCast(this.layoutDoc._currentTimecode))
+            >
+                {!this.path ? (
+                    <div className="audiobox-buttons">
+                        <div className="audiobox-dictation" onClick={this.onFile}>
+                            <FontAwesomeIcon
+                                style={{
+                                    width: "30px",
+                                    background: !this.layoutDoc.dontAutoPlayFollowedLinks
+                                        ? Colors.LIGHT_BLUE
+                                        : "rgba(0,0,0,0)",
+                                }}
+                                icon="file-alt"
+                                size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+                            />
+                        </div>
+                        {this.mediaState === "recording" || this.mediaState === "paused" ? (
+                            <div className="recording" onClick={(e) => e.stopPropagation()}>
+                                <div className="recording-buttons" onClick={this.recordClick}>
+                                    <FontAwesomeIcon
+                                        icon={"stop"}
+                                        size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+                                    />
+                                </div>
+                                <div
+                                    className="recording-buttons"
+                                    onClick={this._paused ? this.recordPlay : this.recordPause}
+                                >
+                                    <FontAwesomeIcon
+                                        icon={this._paused ? "play" : "pause"}
+                                        size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
+                                    />
+                                </div>
+                                <div className="time">
+                                    {formatTime(
+                                        Math.round(NumCast(this.layoutDoc._currentTimecode))
+                                    )}
+                                </div>
+                            </div>
+                        ) : (
+                            <button
+                                className={`audiobox-record${interactive}`}
+                                style={{ backgroundColor: Colors.DARK_GRAY }}
+                            >
+                                RECORD
+                            </button>
+                        )}
+                    </div>
+                ) : (
+                    <div
+                        className="audiobox-controls"
+                        style={{
+                            pointerEvents:
+                                this._isAnyChildContentActive || this.isContentActive()
+                                    ? "all"
+                                    : "none",
+                        }}
+                    >
+                        <div className="audiobox-dictation" />
+                        <div
+                            className="audiobox-player"
+                            style={{ height: `${AudioBox.heightPercent}%` }}
+                        >
+                            <div
+                                className="audiobox-buttons"
+                                title={this.mediaState === "paused" ? "play" : "pause"}
+                                onClick={this.mediaState === "paused" ? this.Play : this.Pause}
+                            >
+                                {" "}
+                                <FontAwesomeIcon
+                                    icon={this.mediaState === "paused" ? "play" : "pause"}
+                                    size={"1x"}
+                                />
+                            </div>
+                            <div
+                                className="audiobox-buttons"
+                                title={this._trimming ? "finish" : "trim"}
+                                onClick={this._trimming ? this.finishTrim : this.startTrim}
+                            >
+                                <FontAwesomeIcon
+                                    icon={this._trimming ? "check" : "cut"}
+                                    size={"1x"}
+                                />
+                            </div>
+                            <div
+                                className="audiobox-timeline"
+                                style={{
+                                    top: 0,
+                                    height: `100%`,
+                                    left: AudioBox.playheadWidth,
+                                    width: `calc(100% - ${AudioBox.playheadWidth}px)`,
+                                    background: "white",
+                                }}
+                            >
+                                {this.renderTimeline}
+                            </div>
+                            {this.audio}
+                            <div className="audioBox-current-time">
+                                {this._trimming ?
+                                    formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))
+                                    : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this._trimStart)))}
+                            </div>
+                            <div className="audioBox-total-time">
+                                {this._trimming || !this._trimEnd ?
+                                    formatTime(Math.round(NumCast(this.duration)))
+                                    : formatTime(Math.round(NumCast(this.trimDuration)))}
+                            </div>
+                        </div>
+                    </div>
                 )}
-              </div>
-              <div className="audioBox-total-time">
-                {formatTime(Math.round(this.duration))}
-              </div>
             </div>
-          </div>
-        )}
-      </div>
-    );
-  }
+        );
+    }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 80a014926..23166335e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,6 +88,7 @@ export interface DocComponentView {
     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;
     setFocus?: () => void;
+    getTitle?: () => string;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 6a7793ff0..8d665b8a6 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -20,11 +20,26 @@ const LabelSchema = createSchema({});
 type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
 const LabelDocument = makeInterface(LabelSchema, documentSchema);
 
+export interface LabelBoxProps {
+    label?: string
+}
+
 @observer
-export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument>(LabelDocument) {
+export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps), LabelDocument>(LabelDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
+    public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) {
+        return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`;  //e.g., "<ImageBox {...props} fieldKey={"data} />"
+    }
     private dropDisposer?: DragManager.DragDropDisposer;
 
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
+
+    getTitle() {
+        return this.props.label || "";
+    }
+
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
         if (ele) {
@@ -65,8 +80,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
     render() {
         const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
         const missingParams = params?.filter(p => !this.paramsDoc[p]);
-        params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... 
-        const label = typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
+        params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
+        const label = this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
         return (
             <div className="labelBox-outerDiv"
                 onMouseLeave={action(() => this._mouseOver = false)}
-- 
cgit v1.2.3-70-g09d2


From be825152070aa609817a2df7880e9cc377f7cfc1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Sep 2021 12:19:38 -0400
Subject: changed ctrl-click to open alias to reuse existing alias.  changed
 documentview to not clearout docView field when invalidated to allow webBox
 componentView to be persistent which allows proper settting of scrollHeight
 and allows webBox to grow to vertical space available in tab or lightbox.

---
 src/client/views/DocumentDecorations.tsx  | 17 ++++++++---------
 src/client/views/SidebarAnnos.tsx         |  3 ++-
 src/client/views/collections/TreeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx   |  7 ++++---
 src/client/views/nodes/WebBox.tsx         | 12 ++++++------
 src/fields/documentSchemas.ts             |  1 -
 6 files changed, 21 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e454be618..a570bdb34 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc, Field, HeightSym, WidthSym, DocListCast } from "../../fields/Doc";
 import { Document } from '../../fields/documentSchemas';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField } from "../../fields/InkField";
@@ -159,9 +159,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         const selectedDocs = SelectionManager.Views();
         if (selectedDocs.length) {
             if (e.ctrlKey) {    // open an alias in a new tab with Ctrl Key
-                selectedDocs[0].props.Document._fullScreenView = Doc.MakeAlias(selectedDocs[0].props.Document);
-                (selectedDocs[0].props.Document._fullScreenView as Doc).context = undefined;
-                CollectionDockingView.AddSplit(selectedDocs[0].props.Document._fullScreenView as Doc, "right");
+                const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+                CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
             } else if (e.shiftKey) {   // open centered in a new workspace with Shift Key
                 const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                 alias.context = undefined;
@@ -169,7 +168,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
                 alias.y = -alias[HeightSym]() / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
             } else if (e.altKey) {    // open same document in new tab
-                CollectionDockingView.ToggleSplit(Cast(selectedDocs[0].props.Document._fullScreenView, Doc, null) || selectedDocs[0].props.Document, "right");
+                CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
             } else {
                 LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document));
             }
@@ -391,10 +390,10 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 Doc.GetProto(doc).data = new InkField(inkPts.map(ipt =>  // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                ({
-                    X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
-                    Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
-                })));
+                    ({
+                        X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+                        Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+                    })));
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1f9763d18..0e3663f65 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -90,6 +90,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         }
         return this.props.styleProvider?.(doc, props, property);
     }
+    setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
     render() {
         const renderTag = (tag: string) => {
             const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
@@ -126,7 +127,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         styleProvider={this.sidebarStyleProvider}
                         docFilters={this.docFilters}
                         scaleField={this.sidebarKey + "-scale"}
-                        setHeight={(height) => this.props.setHeight(height + this.filtersHeight())}
+                        setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         scaling={returnOne}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index e8f345782..b9487054b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -154,7 +154,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         } else {
             // choose an appropriate alias or make one. --- choose the first alias that (1) user owns,  (2) has no context field ... otherwise make a new alias
             // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
-            const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context);
+            const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
             this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
 
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fda84dd2d..6b9513b8b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -93,6 +93,7 @@ export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
+    getScrollHeight?: () => number;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -464,7 +465,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 } else if (!Doc.IsSystem(this.rootDoc)) {
                     UndoManager.RunInBatch(() =>
                         LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.())
-                        //this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox")
                         , "double tap");
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
@@ -983,6 +983,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
     }
     render() {
+        TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
         const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
@@ -1075,7 +1076,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
     @computed get panelHeight() {
         if (this.effectiveNativeHeight) {
-            return Math.min(this.props.PanelHeight(), Math.max(NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
+            return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
         }
         return this.props.PanelHeight();
     }
@@ -1182,7 +1183,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         focus={this.props.focus || emptyFunction}
                         bringToFront={emptyFunction}
-                        ref={action((r: DocumentViewInternal | null) => this.docView = r)} />
+                        ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
                 </div>)}
         </div>);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e4d4557af..719e5a796 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 } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
@@ -13,9 +13,8 @@ import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils, returnEmptyString, returnEmptyFilter } from "../../../Utils";
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
-import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { Scripting } from "../../util/Scripting";
 import { SnappingManager } from "../../util/SnappingManager";
@@ -59,7 +58,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _scrollHeight = 1500;
+    @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight, 1500);
     @computed get _url() { return this.webField?.toString() || ""; }
     @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
     @computed get scrollHeight() { return this._scrollHeight; }
@@ -229,6 +228,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
 
+    getScrollHeight = () => this._scrollHeight;
+
     isFirefox = () => {
         return "InstallTrigger" in window; // navigator.userAgent.indexOf("Chrome") !== -1;
     }
@@ -417,8 +418,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
     }
 
-    @computed
-    get urlContent() {
+    @computed get urlContent() {
         const field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index f17a390a6..db2c6ca5b 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -15,7 +15,6 @@ export const documentSchema = createSchema({
     // "Location" properties in a very general sense
     _curPage: "number",         // current page of a page based document
     _currentFrame: "number",    // current frame of a frame based collection (e.g., a progressive slide)
-    _fullScreenView: Doc,       // alias to display when double-clicking to open document in a full-screen view
     lastFrame: "number",        // last frame of a frame based collection (e.g., a progressive slide)
     activeFrame: "number",      // the active frame of a frame based animated document 
     _currentTimecode: "number", // current play back time of a temporal document (video / audio)
-- 
cgit v1.2.3-70-g09d2


From f3a3536ea4b31cf93748490f4b46a56c4775565f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Sep 2021 15:19:38 -0400
Subject: fixed toggleDetail() func to work.

---
 src/client/views/PropertiesButtons.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx | 13 ++++++-------
 2 files changed, 7 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 86ef9ab62..dcab9bc1e 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -134,7 +134,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             docView.noOnClick();
             switch (e.target.value) {
                 case "enterPortal": docView.makeIntoPortal(); break;
-                case "toggleDetail": docView.toggleDetail(); break;
+                case "toggleDetail": docView.setToggleDetail(); break;
                 case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break;
                 case "linkOnRight": docView.toggleFollowLink("add:right", false, false); break;
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6b9513b8b..20d7a650f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -208,7 +208,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
     @computed get nativeWidth() { return this.props.NativeWidth(); }
     @computed get nativeHeight() { return this.props.NativeHeight(); }
-    @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onfClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
+    @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
     @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
     @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
     @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
@@ -605,7 +605,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
-    @undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`);
+    @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${this.Document.layoutKey}")`, { documentView: "any" });
 
     @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -709,7 +709,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.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
+                onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${this.Document.layoutKey}")`, { documentView: "any" }), 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) {
@@ -1189,8 +1189,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 }
 
-Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: string = "layout") {
-    const dv = DocumentManager.Instance.getDocumentView(doc);
-    if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", ""));
-    else dv?.switchViews(true, layoutKey.replace("layout_", ""));
+Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKey: string, primaryLayoutKey: string = "layout") {
+    if (dv.Document.layoutKey === detailLayoutKey) dv.switchViews(primaryLayoutKey !== "layout", primaryLayoutKey.replace("layout_", ""));
+    else dv.switchViews(true, detailLayoutKey.replace("layout_", ""));
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c8b3de89219a07d466151f5b4633cc87f0d46ec3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Sep 2021 15:32:44 -0400
Subject: from last

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 20d7a650f..7463e192f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -605,7 +605,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
-    @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${this.Document.layoutKey}")`, { documentView: "any" });
+    @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
 
     @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -709,7 +709,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.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${this.Document.layoutKey}")`, { documentView: "any" }), icon: "concierge-bell" });
+                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) {
@@ -1189,7 +1189,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 }
 
-Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKey: string, primaryLayoutKey: string = "layout") {
-    if (dv.Document.layoutKey === detailLayoutKey) dv.switchViews(primaryLayoutKey !== "layout", primaryLayoutKey.replace("layout_", ""));
-    else dv.switchViews(true, detailLayoutKey.replace("layout_", ""));
+Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKey: string) {
+    if (dv.Document.layoutKey === "layout_" + detailLayoutKey) dv.switchViews(false, "layout");
+    else dv.switchViews(true, detailLayoutKey);
 });
\ No newline at end of file
-- 
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/DocumentView.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 01cab1fc7259f1a944fad49403328e70e9d61b97 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Sep 2021 10:16:10 -0400
Subject: from last

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7463e192f..c9482c770 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1189,7 +1189,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 }
 
-Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKey: string) {
-    if (dv.Document.layoutKey === "layout_" + detailLayoutKey) dv.switchViews(false, "layout");
-    else dv.switchViews(true, detailLayoutKey);
+Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
+    if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
+    else dv.switchViews(true, detailLayoutKeySuffix);
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 7075cdfdccbbf2aaa174926cfd6ee894c59d5527 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Sep 2021 10:30:40 -0400
Subject: fixed overlay documents not getting pointer events.

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 501665f3f..3b03643e8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -784,6 +784,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return CurrentUserUtils.SelectedTool !== InkTool.None ||
+            this.props.rootSelected() ||
             this.props.Document.forceActive ||
             this.props.isSelected(outsideReaction) ||
             this._componentView?.isAnyChildContentActive?.() ||
-- 
cgit v1.2.3-70-g09d2


From 4d058ec6e2aaa20f240de5d65680e396f035f36e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Sep 2021 12:00:58 -0400
Subject: fixed dragging things into collections.   reactived fitWidth
 properties button

---
 src/client/views/PropertiesButtons.tsx  | 2 +-
 src/client/views/nodes/DocumentView.tsx | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 286365fa6..0bcdbac19 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -213,7 +213,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.lockButton)}
                 {toggle(this.dictationButton, { display: isNovice ? "none" : "" })}
                 {toggle(this.onClickButton)}
-                {toggle(this.fitWidthButton, { display: isNovice ? "none" : "" })}
+                {toggle(this.fitWidthButton)}
                 {toggle(this.fitContentButton, { display: !isFreeForm ? "none" : "" })}
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })}
                 {toggle(this.maskButton, { display: !isInk ? "none" : "" })}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3b03643e8..b2629200d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -784,6 +784,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return CurrentUserUtils.SelectedTool !== InkTool.None ||
+            SnappingManager.GetIsDragging() ||
             this.props.rootSelected() ||
             this.props.Document.forceActive ||
             this.props.isSelected(outsideReaction) ||
-- 
cgit v1.2.3-70-g09d2


From 610147f7d48ae5a8191fff45cb6d0a5e71bf94a5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Sep 2021 15:25:53 -0400
Subject: cleaned up overlay ink stroke in document decorations

---
 src/client/views/DocumentDecorations.scss |  3 ++
 src/client/views/DocumentDecorations.tsx  | 46 ++++++++++++-------------------
 src/client/views/InkingStroke.tsx         | 33 +++++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx   |  1 +
 4 files changed, 48 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 9479fd365..68e0d12d4 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -7,6 +7,9 @@ $linkGap : 3px;
     z-index: 2000;
 }
 .documentDecorations-inkstroke {
+    position: absolute;
+    overflow: visible;
+    pointer-events: none;
 
   svg:not(:root) {
     overflow: visible !important;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ce33f488a..3ffbb2904 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -457,23 +457,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldoc.rootDoc.type === DocumentType.INK;
-        const docView = seldoc.docView;
-        let selectedLine = null;
-
-        if (useRotation && docView) {
-            const inkData = Cast(seldoc.rootDoc.data, InkField)?.inkData ?? [{ X: 0, Y: 0 }];
-            const inkDoc = seldoc.layoutDoc;
-            const overlayInkWidth = 10;
-
-            const { inkScaleX, inkScaleY, inkStrokeWidth } = InkingStroke.inkScaling(inkDoc, inkData, docView.props.PanelWidth(), docView.props.PanelHeight());
-
-            const screenInkWidth = docView.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
-            const screenPts = inkData.map(point => docView.screenToLocal().inverse().transformPoint(point.X * inkScaleX, point.Y * inkScaleY)).map(p => ({ X: p[0], Y: p[1] }));
-            const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2;
-            const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2;
-
-            selectedLine = InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, overlayInkWidth, overlayInkWidth, StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
-                StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker), StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false);
+        const screenData = seldoc.ComponentView?.screenStrokeData?.();
+        let selectedLine = (null);
+        if (screenData) {
+            const inkDoc = seldoc.props.Document;
+            const overlayWidth = 10;
+            selectedLine = <div className="documentDecorations-inkstroke" style={{
+                width: (bounds.r - bounds.x) + "px",
+                height: (bounds.b - bounds.y) + "px",
+                left: bounds.x,
+                top: bounds.y,
+            }} >
+                {InteractionUtils.CreatePolyline(screenData.screenPts, screenData.screenLeft, screenData.screenTop, Colors.MEDIUM_BLUE, overlayWidth, overlayWidth,
+                    StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
+                    StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker),
+                    StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
+
+            </div>
         }
 
         return (<div className="documentDecorations" style={{ background: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : "" }} >
@@ -516,17 +516,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         </>
                     }
                 </div >
-                <div className="documentDecorations-inkstroke" style={{
-                    position: "absolute",
-                    width: (bounds.r - bounds.x) + "px",
-                    height: (bounds.b - bounds.y) + "px",
-                    left: bounds.x,
-                    top: bounds.y,
-                    overflow: "visible",
-                    pointerEvents: "none",
-                }} >
-                    {selectedLine}
-                </div>
+                {selectedLine}
                 {seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
                     <DocumentButtonBar views={SelectionManager.Views} />
                 </div>}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b8ab0c4d8..8461930bc 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,6 +21,7 @@ import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
 import { Colors } from "./global/globalEnums";
 import { GestureOverlay } from "./GestureOverlay";
+import { isThisTypeNode } from "typescript";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -38,6 +39,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         // this._previousColor = ActiveInkColor();
     }
 
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
+
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -88,8 +93,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         // }
     }
 
-    public static inkScaling(inkDoc: Doc, inkData: InkData, panelWidth: number, panelHeight: number) {
-        const inkStrokeWidth = NumCast(inkDoc.strokeWidth, 1);
+    inkScaledData = () => {
+        const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
         const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2;
@@ -102,10 +108,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             inkLeft,
             inkWidth,
             inkHeight,
-            inkScaleX: inkHeight === inkStrokeWidth ? 1 : (panelWidth - inkStrokeWidth) / (inkWidth - inkStrokeWidth),
-            inkScaleY: inkWidth === inkStrokeWidth ? 1 : (panelHeight - inkStrokeWidth) / (inkHeight - inkStrokeWidth)
+            inkScaleX: inkHeight === inkStrokeWidth ? 1 : (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth),
+            inkScaleY: inkWidth === inkStrokeWidth ? 1 : (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth)
         };
     }
+
+    screenStrokeData = () => {
+        const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const { inkScaleX, inkScaleY, inkStrokeWidth } = this.inkScaledData();
+
+        const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
+        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X * inkScaleX, point.Y * inkScaleY)).map(p => ({ X: p[0], Y: p[1] }));
+        const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2;
+        const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2;
+
+        return { screenPts, screenLeft, screenTop };
+    }
+
     render() {
         TraceMobx();
         this.toggleControlButton();
@@ -114,7 +133,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkDoc: Doc = this.layoutDoc;
 
-        const { inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = InkingStroke.inkScaling(inkDoc, data, this.props.PanelWidth(), this.props.PanelHeight());
+        const { inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
 
         const strokeColor = StrCast(this.layoutDoc.color, "");
         const dotsize = Math.max(inkWidth * inkScaleX, inkHeight * inkScaleY) / 40;
@@ -160,12 +179,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                             inkDoc={inkDoc}
                             data={data}
                             addedPoints={addedPoints}
-                            format={[lineLeft, lineTop, scaleX, scaleY, inkStrokeWidth]}
+                            format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                         <InkHandles
                             inkDoc={inkDoc}
                             data={data}
-                            format={[lineLeft, lineTop, scaleX, scaleY, inkStrokeWidth]}
+                            format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     </> : ""}
             </svg>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 745d58656..3587c1d2a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,6 +88,7 @@ export interface DocComponentView {
     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;
     setFocus?: () => void;
+    screenStrokeData?: () => { screenPts: { X: number, Y: number }[], screenTop: number, screenLeft: number };
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
-- 
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/DocumentView.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 15bc374105eb7c55493d9ca6c2b12f9bf22735c9 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Fri, 3 Sep 2021 13:18:46 -0400
Subject: adding ComponentDecorations

---
 src/client/views/ComponentDecorations.scss |   0
 src/client/views/ComponentDecorations.tsx  |  14 +
 src/client/views/DocumentDecorations.scss  | 631 ++++++++++++++---------------
 src/client/views/DocumentDecorations.tsx   |  19 -
 src/client/views/InkStroke.scss            |  11 +
 src/client/views/InkingStroke.tsx          |  18 +-
 src/client/views/MainView.tsx              |   2 +
 src/client/views/nodes/DocumentView.tsx    |   2 +-
 8 files changed, 354 insertions(+), 343 deletions(-)
 create mode 100644 src/client/views/ComponentDecorations.scss
 create mode 100644 src/client/views/ComponentDecorations.tsx

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

diff --git a/src/client/views/ComponentDecorations.scss b/src/client/views/ComponentDecorations.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
new file mode 100644
index 000000000..66f81fe4f
--- /dev/null
+++ b/src/client/views/ComponentDecorations.tsx
@@ -0,0 +1,14 @@
+import { observer } from "mobx-react";
+import { SelectionManager } from "../util/SelectionManager";
+import './ComponentDecorations.scss';
+import React = require("react");
+
+@observer
+export class ComponentDecorations extends React.Component {
+    static Instance: ComponentDecorations;
+
+    render() {
+        const seldoc = SelectionManager.Views().lastElement();
+        return seldoc?.ComponentView?.componentUI?.() ?? (null);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 68e0d12d4..2e7142a8e 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,419 +1,408 @@
 @import "global/globalCssVariables";
 
-$linkGap : 3px;
+$linkGap: 3px;
 
 .documentDecorations {
-    position: absolute;
-    z-index: 2000;
-}
-.documentDecorations-inkstroke {
-    position: absolute;
-    overflow: visible;
-    pointer-events: none;
-
-  svg:not(:root) {
-    overflow: visible !important;
-  }
+  position: absolute;
+  z-index: 2000;
 }
 .documentDecorations-container {
-    z-index: $docDecorations-zindex;
+  z-index: $docDecorations-zindex;
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: grid;
+  grid-template-rows: 20px 8px 1fr 8px;
+  grid-template-columns: 8px 16px 1fr 8px 8px;
+  pointer-events: none;
+
+  .documentDecorations-centerCont {
+    grid-column: 3;
+    background: none;
+  }
+
+  .documentDecorations-selectorButton {
+    pointer-events: auto;
+    height: 15px;
+    width: 15px;
+    left: -20px;
+    top: 20px;
+    display: inline-block;
     position: absolute;
-    top: 0;
-    left: 0;
-    display: grid;
-    grid-template-rows: 20px 8px 1fr 8px;
-    grid-template-columns: 8px 16px 1fr 8px 8px;
-    pointer-events: none;
-
-    .documentDecorations-centerCont {
-        grid-column: 3;
-        background: none;
-    }
+    opacity: 0.5;
+  }
 
-    .documentDecorations-selectorButton {
-        pointer-events: auto;
-        height: 15px;
-        width: 15px;
-        left: -20px;
-        top: 20px;
-        display: inline-block;
-        position: absolute;
-        opacity: 0.5;
-    }
+  .documentDecorations-radius {
+    pointer-events: auto;
+    opacity: 1;
+    transform: translate(10px, 10px);
+    grid-row: 4;
+  }
 
-    .documentDecorations-radius {
-        pointer-events: auto;
-        opacity: 1;
-        transform: translate(10px, 10px);
-        grid-row: 4;
+  .documentDecorations-topLeftResizer,
+  .documentDecorations-topRightResizer,
+  .documentDecorations-bottomLeftResizer,
+  .documentDecorations-bottomRightResizer,
+  .documentDecorations-leftResizer,
+  .documentDecorations-topResizer,
+  .documentDecorations-bottomResizer,
+  .documentDecorations-rightResizer {
+    pointer-events: auto;
+    background: $medium-gray;
+    opacity: 0.1;
+    &:hover {
+      opacity: 1;
     }
+  }
 
-    .documentDecorations-topLeftResizer,
-    .documentDecorations-topRightResizer,
-    .documentDecorations-bottomLeftResizer,
-    .documentDecorations-bottomRightResizer,
-    .documentDecorations-leftResizer,
-    .documentDecorations-topResizer,
-    .documentDecorations-bottomResizer,
-    .documentDecorations-rightResizer {
-        pointer-events: auto;
-        background: $medium-gray;
-        opacity: 0.1;
-        &:hover {
-            opacity: 1;
-        }
-    }
+  .documentDecorations-topLeftResizer,
+  .documentDecorations-leftResizer,
+  .documentDecorations-bottomLeftResizer {
+    grid-column: 1;
+  }
 
-    .documentDecorations-topLeftResizer,
-    .documentDecorations-leftResizer,
-    .documentDecorations-bottomLeftResizer {
-        grid-column: 1
-    }
+  .documentDecorations-topResizer,
+  .documentDecorations-bottomResizer {
+    grid-column-start: 2;
+    grid-column-end: 5;
+  }
 
-    .documentDecorations-topResizer,
-    .documentDecorations-bottomResizer {
-        grid-column-start: 2;
-        grid-column-end: 5;
-    }
+  .documentDecorations-bottomRightResizer,
+  .documentDecorations-topRightResizer,
+  .documentDecorations-rightResizer {
+    grid-column-start: 5;
+    grid-column-end: 7;
+  }
 
-    .documentDecorations-bottomRightResizer,
-    .documentDecorations-topRightResizer,
-    .documentDecorations-rightResizer {
-        grid-column-start: 5;
-        grid-column-end: 7;
-    }
+  .documentDecorations-rotation,
+  .documentDecorations-borderRadius {
+    grid-column: 5;
+    grid-row: 4;
+    border-radius: 100%;
+    background: black;
+    height: 8;
+    right: -12;
+    top: 12;
+    position: relative;
+    pointer-events: all;
+    cursor: nwse-resize;
 
-    .documentDecorations-rotation,
-    .documentDecorations-borderRadius {
-        grid-column: 5;
-        grid-row: 4;
-        border-radius: 100%;
-        background: black;
-        height: 8;
-        right: -12;
-        top: 12;
-        position: relative;
-        pointer-events: all;
-        cursor: nwse-resize;
-
-        .borderRadiusTooltip {
-            width: 10px;
-            height: 10px;
-            position: absolute;
-        }
+    .borderRadiusTooltip {
+      width: 10px;
+      height: 10px;
+      position: absolute;
     }
-    .documentDecorations-rotation {
-        background: transparent;
-        right: -15;
-    }
-
+  }
+  .documentDecorations-rotation {
+    background: transparent;
+    right: -15;
+  }
 
-    .documentDecorations-topLeftResizer,
-    .documentDecorations-bottomRightResizer {
-        cursor: nwse-resize;
-        background: unset;
-        opacity: 1;
-    }
+  .documentDecorations-topLeftResizer,
+  .documentDecorations-bottomRightResizer {
+    cursor: nwse-resize;
+    background: unset;
+    opacity: 1;
+  }
 
-    .documentDecorations-topLeftResizer {
-        border-left: 2px solid;
-        border-top: solid 2px;
-    }
+  .documentDecorations-topLeftResizer {
+    border-left: 2px solid;
+    border-top: solid 2px;
+  }
 
-    .documentDecorations-bottomRightResizer {
-        border-right: 2px solid;
-        border-bottom: solid 2px;
-    }
+  .documentDecorations-bottomRightResizer {
+    border-right: 2px solid;
+    border-bottom: solid 2px;
+  }
 
-    .documentDecorations-topLeftResizer:hover,
-    .documentDecorations-bottomRightResizer:hover {
-        opacity: 1;
-    }
+  .documentDecorations-topLeftResizer:hover,
+  .documentDecorations-bottomRightResizer:hover {
+    opacity: 1;
+  }
 
-    .documentDecorations-bottomRightResizer {
-        grid-row: 4;
-    }
+  .documentDecorations-bottomRightResizer {
+    grid-row: 4;
+  }
 
-    .documentDecorations-topRightResizer,
-    .documentDecorations-bottomLeftResizer {
-        cursor: nesw-resize;
-        background: unset;
-        opacity: 1;
-    }
+  .documentDecorations-topRightResizer,
+  .documentDecorations-bottomLeftResizer {
+    cursor: nesw-resize;
+    background: unset;
+    opacity: 1;
+  }
 
-    .documentDecorations-topRightResizer {
-        border-right: 2px solid;
-        border-top: 2px solid;
-    }
+  .documentDecorations-topRightResizer {
+    border-right: 2px solid;
+    border-top: 2px solid;
+  }
 
-    .documentDecorations-bottomLeftResizer {
-        border-left: 2px solid;
-        border-bottom: 2px solid;
-    }
+  .documentDecorations-bottomLeftResizer {
+    border-left: 2px solid;
+    border-bottom: 2px solid;
+  }
 
-    .documentDecorations-topRightResizer:hover,
-    .documentDecorations-bottomLeftResizer:hover {
-        cursor: nesw-resize;
-        background: black;
-        opacity: 1;
-    }
+  .documentDecorations-topRightResizer:hover,
+  .documentDecorations-bottomLeftResizer:hover {
+    cursor: nesw-resize;
+    background: black;
+    opacity: 1;
+  }
 
-    .documentDecorations-topResizer,
-    .documentDecorations-bottomResizer {
-        cursor: ns-resize;
-    }
+  .documentDecorations-topResizer,
+  .documentDecorations-bottomResizer {
+    cursor: ns-resize;
+  }
 
-    .documentDecorations-leftResizer,
-    .documentDecorations-rightResizer {
-        cursor: ew-resize;
-    }
+  .documentDecorations-leftResizer,
+  .documentDecorations-rightResizer {
+    cursor: ew-resize;
+  }
 
-    .documentDecorations-contextMenu {
-        width: 25px;
-        height: calc(100% + 8px); // 8px for the height of the top resizer bar
-        grid-column-start: 2;
-        grid-column-end: 2;
-        pointer-events: all;
-        padding-left: 5px;
-        cursor: pointer;
-    }
+  .documentDecorations-contextMenu {
+    width: 25px;
+    height: calc(100% + 8px); // 8px for the height of the top resizer bar
+    grid-column-start: 2;
+    grid-column-end: 2;
+    pointer-events: all;
+    padding-left: 5px;
+    cursor: pointer;
+  }
 
-    .documentDecorations-titleBackground {
-        background: #ffffffcf;
-        border-radius: 8px;
-        width: 100%;
-        height: 100%;
-        position: absolute;
-    }
+  .documentDecorations-titleBackground {
+    background: #ffffffcf;
+    border-radius: 8px;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+  }
 
-    .documentDecorations-title {
-        opacity: 1;
-        grid-column-start: 2;
-        grid-column-end: 4;
-        pointer-events: auto;
-        overflow: hidden;
-        text-align: center;
-        display: flex;
-        margin-left: 5px;
-        height: 22px;
-        position: absolute;
-        .documentDecorations-titleSpan {
-            width: 100%;
-            border-radius: 8px;
-            background: #ffffffcf;
-            position: absolute;
-            display: inline-block;
-            cursor: move;
-        }
+  .documentDecorations-title {
+    opacity: 1;
+    grid-column-start: 2;
+    grid-column-end: 4;
+    pointer-events: auto;
+    overflow: hidden;
+    text-align: center;
+    display: flex;
+    margin-left: 5px;
+    height: 22px;
+    position: absolute;
+    .documentDecorations-titleSpan {
+      width: 100%;
+      border-radius: 8px;
+      background: #ffffffcf;
+      position: absolute;
+      display: inline-block;
+      cursor: move;
     }
+  }
 
-    .focus-visible {
-        margin-left: 0px;
-    }
+  .focus-visible {
+    margin-left: 0px;
+  }
 }
 
-
 .documentDecorations-iconifyButton {
-    opacity: 1;
-    grid-column-start: 4;
-    grid-column-end: 4;
-    pointer-events: all;
-    right: 0;
-    cursor: pointer;
-    position: absolute;
-    width: 20px;
+  opacity: 1;
+  grid-column-start: 4;
+  grid-column-end: 4;
+  pointer-events: all;
+  right: 0;
+  cursor: pointer;
+  position: absolute;
+  width: 20px;
 }
 
 .documentDecorations-openButton {
-    display: flex;
-    align-items: center;
-    opacity: 1;
-    grid-column-start: 5;
-    grid-column-end: 5;
-    pointer-events: all;
-    cursor: pointer;
+  display: flex;
+  align-items: center;
+  opacity: 1;
+  grid-column-start: 5;
+  grid-column-end: 5;
+  pointer-events: all;
+  cursor: pointer;
 }
 
 .documentDecorations-closeButton {
-    display: flex;
-    align-items: center;
-    opacity: 1;
-    grid-column-start: 1;
-    grid-column-end: 3;
-    pointer-events: all;
-    cursor: pointer;
-
-    >svg {
-        margin: 0;
-    }
+  display: flex;
+  align-items: center;
+  opacity: 1;
+  grid-column-start: 1;
+  grid-column-end: 3;
+  pointer-events: all;
+  cursor: pointer;
+
+  > svg {
+    margin: 0;
+  }
 }
 
 .documentDecorations-background {
-    background: lightblue;
-    position: absolute;
-    opacity: 0.1;
+  background: lightblue;
+  position: absolute;
+  opacity: 0.1;
 }
 
 .linkFlyout {
-    grid-column: 2/4;
+  grid-column: 2/4;
 }
 
 .linkButton-empty:hover {
-    background: $medium-gray;
-    transform: scale(1.05);
-    cursor: pointer;
+  background: $medium-gray;
+  transform: scale(1.05);
+  cursor: pointer;
 }
 
 .linkButton-nonempty:hover {
-    background: $medium-gray;
-    transform: scale(1.05);
-    cursor: pointer;
+  background: $medium-gray;
+  transform: scale(1.05);
+  cursor: pointer;
 }
 
 .link-button-container {
-    border-radius: 10px;
-    width: max-content;
-    height: auto;
-    display: flex;
-    flex-direction: row;
-    z-index: 998;
-    position: absolute;
-    justify-content: center;
-    align-items: center;
-    gap: 5px;
-    background: $medium-gray;
+  border-radius: 10px;
+  width: max-content;
+  height: auto;
+  display: flex;
+  flex-direction: row;
+  z-index: 998;
+  position: absolute;
+  justify-content: center;
+  align-items: center;
+  gap: 5px;
+  background: $medium-gray;
 }
 
 .linkButtonWrapper {
-    pointer-events: auto;
-    padding-right: 5px;
-    width: 25px;
+  pointer-events: auto;
+  padding-right: 5px;
+  width: 25px;
 }
 
 .linkButton-linker {
-    height: 20px;
-    width: 20px;
-    text-align: center;
-    border-radius: 50%;
-    pointer-events: auto;
-    color: $dark-gray;
-    border: $dark-gray 1px solid;
+  height: 20px;
+  width: 20px;
+  text-align: center;
+  border-radius: 50%;
+  pointer-events: auto;
+  color: $dark-gray;
+  border: $dark-gray 1px solid;
 }
 
 .linkButton-linker:hover {
-    cursor: pointer;
-    transform: scale(1.05);
+  cursor: pointer;
+  transform: scale(1.05);
 }
 
 .linkButton-empty,
 .linkButton-nonempty {
-    height: 20px;
-    width: 20px;
-    border-radius: 50%;
-    opacity: 0.9;
-    pointer-events: auto;
-    background-color: $dark-gray;
-    color: $white;
-    text-transform: uppercase;
-    letter-spacing: 2px;
-    font-size: 75%;
-    transition: transform 0.2s;
-    text-align: center;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-
-    &:hover {
-        background: $medium-gray;
-        transform: scale(1.05);
-        cursor: pointer;
-    }
+  height: 20px;
+  width: 20px;
+  border-radius: 50%;
+  opacity: 0.9;
+  pointer-events: auto;
+  background-color: $dark-gray;
+  color: $white;
+  text-transform: uppercase;
+  letter-spacing: 2px;
+  font-size: 75%;
+  transition: transform 0.2s;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  &:hover {
+    background: $medium-gray;
+    transform: scale(1.05);
+    cursor: pointer;
+  }
 }
 
 .templating-menu {
-    position: absolute;
-    pointer-events: auto;
-    text-transform: uppercase;
-    letter-spacing: 2px;
-    font-size: 75%;
-    transition: transform 0.2s;
-    text-align: center;
-    display: flex;
-    justify-content: center;
-    align-items: center;
+  position: absolute;
+  pointer-events: auto;
+  text-transform: uppercase;
+  letter-spacing: 2px;
+  font-size: 75%;
+  transition: transform 0.2s;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 .documentdecorations-icon {
-    margin: 0px;
+  margin: 0px;
 }
 .templating-button,
 .docDecs-tagButton {
-    width: 20px;
-    height: 20px;
-    border-radius: 50%;
-    opacity: 0.9;
-    font-size: 14;
-    background-color: $dark-gray;
-    color: $white;
-    text-align: center;
-    cursor: pointer;
-
-    &:hover {
-        background: $medium-gray;
-        transform: scale(1.05);
-    }
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  opacity: 0.9;
+  font-size: 14;
+  background-color: $dark-gray;
+  color: $white;
+  text-align: center;
+  cursor: pointer;
+
+  &:hover {
+    background: $medium-gray;
+    transform: scale(1.05);
+  }
 }
 
 .documentDecorations-darkScheme {
-    background: dimgray;
+  background: dimgray;
 }
 
 #template-list {
-    position: absolute;
-    top: 25px;
-    left: 0px;
-    width: max-content;
-    font-family: $sans-serif;
-    font-size: 12px;
-    background-color: $light-gray;
-    padding: 2px 12px;
-    list-style: none;
-
-    .templateToggle,
-    .chromeToggle {
-        text-align: left;
-    }
+  position: absolute;
+  top: 25px;
+  left: 0px;
+  width: max-content;
+  font-family: $sans-serif;
+  font-size: 12px;
+  background-color: $light-gray;
+  padding: 2px 12px;
+  list-style: none;
+
+  .templateToggle,
+  .chromeToggle {
+    text-align: left;
+  }
 
-    input {
-        margin-right: 10px;
-    }
+  input {
+    margin-right: 10px;
+  }
 }
 
 @-moz-keyframes spin {
-    100% {
-        -moz-transform: rotate(360deg);
-    }
+  100% {
+    -moz-transform: rotate(360deg);
+  }
 }
 
 @-webkit-keyframes spin {
-    100% {
-        -webkit-transform: rotate(360deg);
-    }
+  100% {
+    -webkit-transform: rotate(360deg);
+  }
 }
 
 @keyframes spin {
-    100% {
-        -webkit-transform: rotate(360deg);
-        transform: rotate(360deg);
-    }
+  100% {
+    -webkit-transform: rotate(360deg);
+    transform: rotate(360deg);
+  }
 }
 
 @keyframes shadow-pulse {
-    0% {
-        box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
-    }
+  0% {
+    box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
+  }
 
-    100% {
-        box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
-    }
-}
\ No newline at end of file
+  100% {
+    box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
+  }
+}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3ffbb2904..2348c897b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -457,24 +457,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldoc.rootDoc.type === DocumentType.INK;
-        const screenData = seldoc.ComponentView?.screenStrokeData?.();
-        let selectedLine = (null);
-        if (screenData) {
-            const inkDoc = seldoc.props.Document;
-            const overlayWidth = 10;
-            selectedLine = <div className="documentDecorations-inkstroke" style={{
-                width: (bounds.r - bounds.x) + "px",
-                height: (bounds.b - bounds.y) + "px",
-                left: bounds.x,
-                top: bounds.y,
-            }} >
-                {InteractionUtils.CreatePolyline(screenData.screenPts, screenData.screenLeft, screenData.screenTop, Colors.MEDIUM_BLUE, overlayWidth, overlayWidth,
-                    StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
-                    StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker),
-                    StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
-
-            </div>
-        }
 
         return (<div className="documentDecorations" style={{ background: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : "" }} >
             <div className="documentDecorations-background" style={{
@@ -516,7 +498,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         </>
                     }
                 </div >
-                {selectedLine}
                 {seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
                     <DocumentButtonBar views={SelectionManager.Views} />
                 </div>}
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index 812a79bd5..8edefa07b 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -1,3 +1,14 @@
+.inkstroke-UI {
+  //   transform-origin: top left;
+  position: absolute;
+  overflow: visible;
+  pointer-events: none;
+
+  svg:not(:root) {
+    overflow: visible !important;
+  }
+}
+
 .inkStroke {
   mix-blend-mode: multiply;
   stroke-linejoin: round;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 297b3333c..41fbe0d55 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -114,7 +114,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         };
     }
 
-    screenStrokeData = () => {
+    componentUI = () => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth } = this.inkScaledData();
 
         const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
@@ -122,7 +122,21 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2;
         const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2;
 
-        return { screenPts, screenLeft, screenTop };
+        const inkDoc = this.props.Document;
+        
+        const overlayWidth = 5;
+        return <div className="inkstroke-UI" style={{
+            // width: (bounds.r - bounds.x) + "px",
+            // height: (bounds.b - bounds.y) + "px",
+            left: screenLeft,
+            top: screenTop,
+        }} >
+            {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, overlayWidth, overlayWidth,
+                StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
+                StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker),
+                StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
+
+        </div>
     }
 
     render() {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1ce363b0a..b81459075 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -39,6 +39,7 @@ import { CollectionViewType } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
+import { ComponentDecorations } from './ComponentDecorations';
 import { GestureOverlay } from './GestureOverlay';
 import { MENU_PANEL_WIDTH, SEARCH_PANEL_HEIGHT } from './global/globalCssVariables.scss';
 import { KeyManager } from './GlobalKeyHandler';
@@ -594,6 +595,7 @@ export class MainView extends React.Component {
             <GroupManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} boundsLeft={this.leftOffset} boundsTop={this.topOffset} />
+            <ComponentDecorations />
             {this.search}
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3587c1d2a..4e6ed40d1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,7 +88,7 @@ export interface DocComponentView {
     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;
     setFocus?: () => void;
-    screenStrokeData?: () => { screenPts: { X: number, Y: number }[], screenTop: number, screenLeft: number };
+    componentUI?: () => JSX.Element;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
-- 
cgit v1.2.3-70-g09d2


From 9a4bd3ca5dbe192eec20bf405a9904ef8b546de6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 4 Sep 2021 00:46:41 -0400
Subject: added top/left clip bounds to ink decorations

---
 src/client/views/ComponentDecorations.tsx | 4 ++--
 src/client/views/InkingStroke.tsx         | 3 ++-
 src/client/views/MainView.tsx             | 4 ++--
 src/client/views/nodes/DocumentView.tsx   | 2 +-
 4 files changed, 7 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
index 66f81fe4f..66d1bd63d 100644
--- a/src/client/views/ComponentDecorations.tsx
+++ b/src/client/views/ComponentDecorations.tsx
@@ -4,11 +4,11 @@ import './ComponentDecorations.scss';
 import React = require("react");
 
 @observer
-export class ComponentDecorations extends React.Component {
+export class ComponentDecorations extends React.Component<{ boundsTop: number, boundsLeft: number }, { value: string }> {
     static Instance: ComponentDecorations;
 
     render() {
         const seldoc = SelectionManager.Views().lastElement();
-        return seldoc?.ComponentView?.componentUI?.() ?? (null);
+        return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? (null);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 90eb52dd2..e713cedee 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -114,7 +114,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         };
     }
 
-    componentUI = () => {
+    componentUI = (boundsLeft: number, boundsTop: number) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth } = this.inkScaledData();
 
         const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
@@ -129,6 +129,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         return <div className="inkstroke-UI" style={{
             left: screenOrigin[0],
             top: screenOrigin[1],
+            clip: `rect(${boundsTop - screenOrigin[1]}px, 10000px, 10000px, ${boundsLeft - screenOrigin[0]}px)`
         }} >
             {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], overlayWidth,
                 StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b81459075..f03124f0d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -83,7 +83,7 @@ export class MainView extends React.Component {
     @observable private _sidebarContent: any = this.userDoc?.sidebar;
     @observable private _flyoutWidth: number = 0;
 
-    @computed private get topOffset() { return Number(SEARCH_PANEL_HEIGHT.replace("px", "")); } //TODO remove
+    @computed private get topOffset() { return 35 + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); } //TODO remove
     @computed private get leftOffset() { return this.menuPanelWidth() - 2; }
     @computed private get userDoc() { return Doc.UserDoc(); }
     @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
@@ -595,7 +595,7 @@ export class MainView extends React.Component {
             <GroupManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} boundsLeft={this.leftOffset} boundsTop={this.topOffset} />
-            <ComponentDecorations />
+            <ComponentDecorations boundsLeft={this.leftOffset} boundsTop={this.topOffset + 25} />
             {this.search}
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4e6ed40d1..a9b303294 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,7 +88,7 @@ export interface DocComponentView {
     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;
     setFocus?: () => void;
-    componentUI?: () => JSX.Element;
+    componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
-- 
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/DocumentView.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 ca8cc74157adb339c15161cacf567c726dd15d1a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Sep 2021 14:00:10 -0400
Subject: from last (fixed text color for headers)

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 384d2e456..07e0e2d52 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { AudioField } from "../../../fields/URLField";
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -953,11 +953,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 />
             </div>;
         const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
+        const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
         const titleView = !showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
                 position: this.headerMargin ? "relative" : "absolute",
                 height: this.titleHeight,
-                background: StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"),
+                color: lightOrDark(background),
+                background,
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
-- 
cgit v1.2.3-70-g09d2


From 57501970d9b56ed39c6b29680286d66a536df5f1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Sep 2021 18:55:32 -0400
Subject: added showTitle prop to allow sidebarAnnos to have a custom title

---
 src/client/views/SidebarAnnos.tsx                         | 10 ++--------
 src/client/views/StyleProvider.tsx                        |  9 ++++++---
 src/client/views/collections/CollectionStackingView.tsx   | 15 ++++++++-------
 .../collections/CollectionStackingViewFieldColumn.tsx     |  2 +-
 src/client/views/collections/CollectionView.tsx           |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx         |  1 +
 src/client/views/nodes/DocumentView.tsx                   | 15 +++++++++++----
 7 files changed, 30 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 01b79ffd2..dd851c764 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -83,13 +83,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
     docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
-
-    sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
-        if (property === StyleProp.ShowTitle) {
-            return doc === this.props.rootDoc ? undefined : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
-        }
-        return this.props.styleProvider?.(doc, props, property);
-    }
+    showTitle = () => "title";
     setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
     render() {
         const renderTag = (tag: string) => {
@@ -125,13 +119,13 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         NativeHeight={returnZero}
                         PanelHeight={this.panelHeight}
                         PanelWidth={this.panelWidth}
-                        styleProvider={this.sidebarStyleProvider}
                         docFilters={this.docFilters}
                         scaleField={this.sidebarKey + "-scale"}
                         setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         scaling={returnOne}
+                        childShowTitle={this.showTitle}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                         childHideDecorationTitle={returnTrue}
                         removeDocument={this.removeDocument}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index e528e84e3..3413179ed 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -93,9 +93,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         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"]);
-        case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle,
-            !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
-                (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || "";
+        case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
+            StrCast(doc._showTitle,
+                props?.showTitle?.() ||
+                (!Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
+                    (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
+                        "author;creationDate") : "")) || "");
         case StyleProp.Color:
             if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index c92b259d0..540bfd1ef 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -236,6 +236,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
+            showTitle={this.props.childShowTitle}
             dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
@@ -517,14 +518,14 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     }
 
     @computed get buttonMenu() {
-        const menuDoc:Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
         // TODO:glr Allow support for multiple buttons
-        if (menuDoc){
+        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}}>
+                style={{ width: width, height: height }}>
                 <DocumentView
                     Document={menuDoc}
                     DataDoc={menuDoc}
@@ -550,7 +551,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
                     ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined} 
+                    ContainingCollectionDoc={undefined}
                 />
             </div>
             );
@@ -579,7 +580,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             <>
                 {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
                     {buttonMenu ? this.buttonMenu : null}
-                    {Doc.UserDoc().noviceMode && noviceExplainer ? 
+                    {Doc.UserDoc().noviceMode && noviceExplainer ?
                         <div className="documentExplanation">
                             {noviceExplainer}
                         </div>
@@ -612,10 +613,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                                 checkedChildren="edit"
                                 unCheckedChildren="view"
                             />} */}
-                    </div> 
+                    </div>
                 </div>
             </>
-            
+
         );
     }
 }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 47733994b..58289a161 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -111,7 +111,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
     @action pointerLeave = () => this._background = "inherit";
-    textCallback = (char: string) => this.addNewTextDoc("", false, true);
+    textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
 
     @action
     addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 81375e557..bc02c44f0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -71,6 +71,7 @@ export interface CollectionViewProps extends FieldViewProps {
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
     childFitWidth?: () => boolean;
+    childShowTitle?: () => string;
     childOpacity?: () => number;
     childContextMenuItems?: () => { script: ScriptField, label: string }[];
     childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 018c2a895..c8561d901 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1052,6 +1052,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             freezeDimensions={this.props.childFreezeDimensions}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             bringToFront={this.bringToFront}
+            showTitle={this.props.childShowTitle}
             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}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 07e0e2d52..6eeb51ffe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -114,6 +114,7 @@ export interface DocumentViewSharedProps {
     docFilters: () => string[];
     docRangeFilters: () => string[];
     searchFilterDocs: () => Doc[];
+    showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
     addDocTab: (doc: Doc, where: string) => boolean;
@@ -703,9 +704,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
                 const zorders = cm.findByDescription("ZOrder...");
                 const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
-                zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
-                zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
-                zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+                if (this.props.bringToFront !== emptyFunction) {
+                    zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
+                    zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
+                    zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+                }
                 !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
 
                 onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -974,7 +977,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             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";
+                            if (this.props.showTitle) {
+                                this.rootDoc._showTitle = value?.substring(1) ? value.substring(1) : undefined;
+                            } else {
+                                Doc.UserDoc().showTitle = value?.substring(1) ? value.substring(1) : "creationDate";
+                            }
                             return true;
                         }
                         return true;
-- 
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/DocumentView.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 9d177851ecaaea1a89ac9b7de1cec16368603eaa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Sep 2021 22:49:13 -0400
Subject: added SelectionManager.Docs() to avoid having to dereference props to
 get at the seelcted Doc.  this was causing a cycle in menu items when
 selected.  fixed sizing of filterBox.  enabled showing titles for VID,COL,and
 IMG in addition to RTF.

---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/util/HypothesisUtils.ts                 |  2 +-
 src/client/util/SelectionManager.ts                | 12 ++--
 src/client/views/GlobalKeyHandler.ts               |  4 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/collections/CollectionMenu.tsx    | 21 +++----
 src/client/views/collections/CollectionSubView.tsx |  8 +--
 .../collectionLinear/CollectionLinearView.tsx      | 19 +++---
 src/client/views/nodes/DocumentView.tsx            |  5 +-
 src/client/views/nodes/FilterBox.tsx               | 11 ++--
 src/client/views/nodes/button/FontIconBox.tsx      | 68 ++++++++++------------
 .../views/nodes/formattedText/RichTextMenu.tsx     | 20 +++----
 13 files changed, 81 insertions(+), 95 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8435825c9..60bb375c8 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1616,7 +1616,7 @@ Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
     return [];
 });
 Scripting.addGlobal(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) {
-    let selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined;
     if (selected && checkParent) {
         const parentDoc: Doc = Cast(selected.context, Doc, null);
         selected = parentDoc;
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 635673025..e910a9118 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -29,7 +29,7 @@ export namespace Hypothesis {
      * Search for a WebDocument whose url field matches the given uri, return undefined if not found
      */
     export const findWebDoc = async (uri: string) => {
-        const currentDoc = SelectionManager.Views().length && SelectionManager.Views()[0].props.Document;
+        const currentDoc = SelectionManager.Docs().lastElement();
         if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
 
         const results: Doc[] = [];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0cfaebbf2..bac13373c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -11,7 +11,7 @@ export namespace SelectionManager {
     class Manager {
 
         @observable IsDragging: boolean = false;
-        SelectedViews: ObservableMap<DocumentView, boolean> = new ObservableMap();
+        SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
         @observable SelectedSchemaDocument: Doc | undefined;
         @observable SelectedSchemaCollection: CollectionSchemaView | undefined;
 
@@ -28,19 +28,18 @@ export namespace SelectionManager {
                     this.DeselectAll();
                 }
 
-                manager.SelectedViews.set(docView, true);
+                manager.SelectedViews.set(docView, docView.rootDoc);
                 docView.props.whenChildContentsActiveChanged(true);
             } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) {
                 Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
                 manager.SelectedSchemaCollection = undefined;
                 manager.SelectedViews.clear();
-                manager.SelectedViews.set(docView, true);
+                manager.SelectedViews.set(docView, docView.rootDoc);
             }
         }
         @action
         DeselectView(docView: DocumentView): void {
-
             if (manager.SelectedViews.get(docView)) {
                 manager.SelectedViews.delete(docView);
                 docView.props.whenChildContentsActiveChanged(false);
@@ -92,7 +91,7 @@ export namespace SelectionManager {
     }
 
     export function Views(): Array<DocumentView> {
-        return Array.from(manager.SelectedViews.keys()).filter(dv => dv.props.Document._viewType !== CollectionViewType.Docking);
+        return Array.from(manager.SelectedViews.keys()).filter(dv => manager.SelectedViews.get(dv)?._viewType !== CollectionViewType.Docking);
     }
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
@@ -100,4 +99,7 @@ export namespace SelectionManager {
     export function SelectedSchemaCollection(): CollectionSchemaView | undefined {
         return manager.SelectedSchemaCollection;
     }
+    export function Docs(): Doc[] {
+        return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 0127d3080..f66c9c788 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -229,8 +229,8 @@ export class KeyManager {
                 }
                 break;
             case "o":
-                const target = SelectionManager.Views()[0];
-                target && CollectionDockingView.OpenFullScreen(target.props.Document);
+                const target = SelectionManager.Docs().lastElement();
+                target && CollectionDockingView.OpenFullScreen(target);
                 break;
             case "r":
                 preventDefault = false;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 09e12443b..ab9022a84 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -366,7 +366,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
             <div className="expansion-button" onPointerDown={() => {
                 if (this.selectedDocumentView || this.selectedDoc) {
-                    SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc);
+                    SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
                 }
             }}>
                 <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3413179ed..3c88a4830 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -96,7 +96,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
             StrCast(doc._showTitle,
                 props?.showTitle?.() ||
-                (!Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
+                (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
                     (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
                         "author;creationDate") : "")) || "");
         case StyleProp.Color:
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index ce5846502..aefa1ec3d 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -62,7 +62,7 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentDidMount() {
         reaction(() => SelectionManager.Views().length && SelectionManager.Views()[0],
-            (doc) => doc && this.SetSelection(doc));
+            view => view && this.SetSelection(view));
     }
 
     @action
@@ -441,10 +441,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
         </div>);
     }
 
-    @computed get selectedDocumentView() {
-        return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    }
-    @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+    @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+    @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
     @computed get notACollection() {
         if (this.selectedDoc) {
             const layoutField = Doc.LayoutField(this.selectedDoc);
@@ -552,8 +550,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
     @undoBatch
     onAliasButtonMoved = (e: PointerEvent) => {
         const contentDiv = this.selectedDocumentView?.ContentDiv;
-        if (contentDiv) {
-            const dragData = new DragManager.DocumentDragData([this.selectedDocumentView!.props.Document]);
+        if (contentDiv && this.selectedDoc) {
+            const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
             const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
             dragData.defaultDropAction = "alias";
             dragData.canEmbed = true;
@@ -648,11 +646,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
         return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")];
     }
     @computed get childDocs() { return DocListCast(this.dataField); }
-    @computed get selectedDocumentView() { return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; }
-    @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
-    @computed get isText() {
-        return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
-    }
+    @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+    @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
+    @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; }
 
     @undoBatch
     @action
@@ -826,7 +822,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
 
             </div>;
     }
-    @observable viewType = this.selectedDoc?._viewType;
 
     render() {
         return !this.props.docView.layoutDoc ? (null) :
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b70df93da..06d20f015 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -321,12 +321,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                                 }
                             });
                         } else {
-                            let srcUrl: string | undefined;
-                            let srcWeb: Doc | undefined;
-                            if (SelectionManager.Views().length) {
-                                srcWeb = SelectionManager.Views()[0].props.Document;
-                                srcUrl = (srcWeb.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
-                            }
+                            const srcWeb = SelectionManager.Docs().lastElement();
+                            const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
                             const reg = new RegExp(Utils.prepend(""), "g");
                             const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
                             const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index fae92e076..7fe95fef0 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,24 +1,24 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, HeightSym, WidthSym, Opt } from '../../../../fields/Doc';
+import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
 import { documentSchema } from '../../../../fields/documentSchemas';
 import { Id } from '../../../../fields/FieldSymbols';
 import { makeInterface } from '../../../../fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
+import { Colors, Shadows } from '../../global/globalEnums';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
 import { StyleProp } from '../../StyleProvider';
-import "./CollectionLinearView.scss";
 import { CollectionSubView } from '../CollectionSubView';
 import { CollectionViewType } from '../CollectionView';
-import { Colors, Shadows } from '../../global/globalEnums';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./CollectionLinearView.scss";
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -115,9 +115,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     }
 
 
-    getDisplayDoc(doc: Doc) {
+    getDisplayDoc = (doc: Doc) => {
         const nested = doc._viewType === CollectionViewType.Linear;
-        const hidden = doc.hidden === true;;
+        const hidden = doc.hidden === true;
 
         let dref: Opt<HTMLDivElement>;
         const docXf = () => this.getTransform(dref);
@@ -142,8 +142,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                 pinToPres={emptyFunction}
                 rootSelected={this.props.isSelected}
                 removeDocument={this.props.removeDocument}
-                // ScreenToLocalTransform={docXf} // bcz: Ugh -- this is what we want, but it breaks things when right-clicking on linear view item since docXf is not a constant value across invalidations
-                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ScreenToLocalTransform={docXf}
                 PanelWidth={nested ? doc[WidthSym] : this.dimension}
                 PanelHeight={nested ? doc[HeightSym] : this.dimension}
                 renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6f97cdbd8..8b19fb204 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -649,7 +649,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+            //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -952,11 +952,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     hideOnLeave={true}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
+                    isContentActive={this.isContentActive}
                     onClick={this.onClickFunc}
                 />
             </div>;
         const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
-        const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
+        const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
         const titleView = !showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
                 position: this.headerMargin ? "relative" : "absolute",
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 7ad03e055..e9f19bf9e 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../fields/List";
 import { RichTextField } from "../../../fields/RichTextField";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../fields/Types";
+import { Cast, StrCast, NumCast } from "../../../fields/Types";
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
@@ -79,7 +79,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
     * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
     */
     @computed static get targetDoc() {
-        return SelectionManager.Views().length ? SelectionManager.Views()[0].Document : CurrentUserUtils.ActiveDashboard;
+        return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : CurrentUserUtils.ActiveDashboard;
     }
     @computed static get targetDocChildKey() {
         if (SelectionManager.Views().length) {
@@ -88,10 +88,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
         return "data";
     }
     @computed static get targetDocChildren() {
-        if (SelectionManager.Views().length) {
-            return DocListCast(FilterBox.targetDoc[FilterBox.targetDocChildKey]);
-        }
-        return DocListCast(CurrentUserUtils.ActiveDashboard.data);
+        return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data);
     }
 
     @observable _loaded = false;
@@ -347,7 +344,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
         );
     }
     setTreeHeight = (hgt: number) => {
-        this.layoutDoc._height = hgt + 140; // 50?  need to add all the border sizes together.
+        this.layoutDoc._height = NumCast(this.layoutDoc._autoHeightMargins) + 150; // need to add all the border sizes together.
     }
 
     /**
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 4475cf347..df7c54f67 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -248,14 +248,15 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         let noneSelected: boolean = false;
 
         if (script === 'setView') {
-            const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-            if (selected && StrCast(selected.Document.type) === DocumentType.COL) {
-                text = StrCast(selected.Document._viewType);
-            } else if (selected) {
-                dropdown = false;
-                text = StrCast(selected.Document.type);
-                if (text === DocumentType.RTF) text = "Text";
-                icon = Doc.toIcon(selected.Document);
+            const selected = SelectionManager.Docs().lastElement();
+            if (selected) {
+                if (StrCast(selected.type) === DocumentType.COL) {
+                    text = StrCast(selected._viewType);
+                } else {
+                    dropdown = false;
+                    text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type);
+                    icon = Doc.toIcon(selected);
+                }
             } else {
                 dropdown = false;
                 icon = "globe-asia";
@@ -263,13 +264,8 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
         } else if (script === 'setFont') {
-            const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-            if (selected && StrCast(selected.Document.type) === DocumentType.RTF) {
-                text = StrCast(selected.Document._fontFamily);
-            } else {
-                const fontFamily = StrCast(Doc.UserDoc()._fontFamily);
-                text = fontFamily;
-            }
+            const selected = SelectionManager.Docs().lastElement();
+            text = StrCast((selected?.type === DocumentType.RTF ? selected : Doc.UserDoc())._fontFamily);
             noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
                 "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
         }
@@ -584,13 +580,13 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setView(view: string) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
+    const selected = SelectionManager.Docs().lastElement();
+    selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
 });
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    const selected = SelectionManager.Docs().lastElement();
     if (checkResult) {
         if (selected) {
             console.log("[Background] (selected): " + StrCast(selected._backgroundColor));
@@ -635,7 +631,7 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) {
-    SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font);
+    SelectionManager.Docs().map(doc => doc._fontFamily = font);
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
     editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView);
     Doc.UserDoc()._fontFamily = font;
@@ -665,7 +661,7 @@ Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", ch
         if (active === align) return Colors.MEDIUM_BLUE;
         else return "transparent";
     }
-    SelectionManager.Views().map(dv => dv.props.Document.textAlign = align);
+    SelectionManager.Docs().map(doc => doc.textAlign = align);
     switch (align) {
         case "left":
             editorView?.state && RichTextMenu.Instance.alignLeft(editorView, editorView.dispatch);
@@ -703,13 +699,13 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+    const selected = SelectionManager.Docs().lastElement();
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
 
     if (checkResult) {
         if (selected) {
-            console.log("[Font color] (selected): " + StrCast(selected.rootDoc._fontColor));
-            return selected.rootDoc._fontColor;
+            console.log("[Font color] (selected): " + StrCast(selected._fontColor));
+            return selected._fontColor;
         } else {
             console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor));
             return Doc.UserDoc()._fontColor;
@@ -717,7 +713,7 @@ Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean)
     }
 
     if (selected) {
-        selected.rootDoc._fontColor = color;
+        selected._fontColor = color;
         if (color) {
             editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
         }
@@ -727,18 +723,18 @@ Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean)
 
 // 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 selected = SelectionManager.Docs().lastElement();
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
 
     if (checkResult) {
         if (selected) {
-            return selected.Document._fontHighlight;
+            return selected._fontHighlight;
         } else {
             return Doc.UserDoc()._fontHighlight;
         }
     }
     if (selected) {
-        selected.rootDoc._fontColor = color;
+        selected._fontColor = color;
         if (color) {
             editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
         }
@@ -769,7 +765,7 @@ Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
         console.log("editorView");
         editorView?.state && RichTextMenu.Instance.toggleBold(editorView, true);
     }
-    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.bold = !dv.props.Document.bold);
+    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.bold = !doc.bold);
     Doc.UserDoc().bold = !Doc.UserDoc().bold;
     return Doc.UserDoc().bold;
 });
@@ -784,7 +780,7 @@ Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
         console.log("editorView");
         editorView?.state && RichTextMenu.Instance.toggleUnderline(editorView, true);
     }
-    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.underline = !dv.props.Document.underline);
+    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.underline = !doc.underline);
     Doc.UserDoc().underline = !Doc.UserDoc().underline;
     return Doc.UserDoc().underline;
 });
@@ -799,7 +795,7 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
         console.log("editorView");
         editorView?.state && RichTextMenu.Instance.toggleItalic(editorView, true);
     }
-    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.RTF).map(dv => dv.props.Document.italic = !dv.props.Document.italic);
+    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.italic = !doc.italic);
     Doc.UserDoc().italic = !Doc.UserDoc().italic;
     return Doc.UserDoc().italic;
 });
@@ -841,7 +837,7 @@ Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolea
 
 Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) {
     if (checkResult) {
-        const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+        const selected = SelectionManager.Docs().lastElement();
         if (selected && selected.type === DocumentType.INK) {
             return Number(selected.strokeWidth);
         } else {
@@ -850,13 +846,13 @@ Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean
         }
     }
     Doc.UserDoc().activeInkWidth = width;
-    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(width));
+    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.INK).map(doc => doc.strokeWidth = Number(width));
 });
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
-        const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+        const selected = SelectionManager.Docs().lastElement();
         if (selected && selected.type === DocumentType.INK) {
             return selected.color;
         } else {
@@ -865,7 +861,7 @@ Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolea
         }
     }
     SetActiveInkColor(StrCast(color));
-    SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.color = String(color));
+    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.INK).map(doc => doc.color = String(color));
 });
 
 
@@ -873,7 +869,7 @@ Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolea
  *      webSetURL
  **/
 Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    const selected = SelectionManager.Docs().lastElement();
     console.log("URL: ", url);
     if (checkResult && selected && selected.type === DocumentType.WEB) {
         return Cast(selected.data, WebField, null).url;
@@ -888,7 +884,7 @@ Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) {
  *      toggleSchemaPreview
  **/
 Scripting.addGlobal(function toggleSchemaPreview(checkResult?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined;
+    const selected = SelectionManager.Docs().lastElement();
     console.log(selected && selected.title);
     if (checkResult && selected) {
         const result: boolean = NumCast(selected.schemaPreviewWidth) > 0;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3d9d9543d..3919fbf94 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -279,33 +279,33 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         });
     }
 
-    toggleBold = (view: EditorView, forceBool?:boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.strong, {strong: forceBool});
+    toggleBold = (view: EditorView, forceBool?: boolean) => {
+        const mark = view.state.schema.mark(view.state.schema.marks.strong, { strong: forceBool });
         this.setMark(mark, view.state, view.dispatch, false);
         view.focus();
     }
 
-    toggleUnderline = (view: EditorView, forceBool?:boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.underline, {underline: forceBool});
+    toggleUnderline = (view: EditorView, forceBool?: boolean) => {
+        const mark = view.state.schema.mark(view.state.schema.marks.underline, { underline: forceBool });
         this.setMark(mark, view.state, view.dispatch, false);
         view.focus();
     }
 
-    toggleItalic = (view: EditorView, forceBool?:boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.em, {em: forceBool});
+    toggleItalic = (view: EditorView, forceBool?: boolean) => {
+        const mark = view.state.schema.mark(view.state.schema.marks.em, { em: forceBool });
         this.setMark(mark, view.state, view.dispatch, false);
         view.focus();
     }
 
 
-    setFontSize = (size:number, view: EditorView) => {
+    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);
     }
 
-    setFontFamily = (family:string, view: EditorView) => {
+    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();
@@ -516,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
     get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
 
-    
+
 
     @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
 
@@ -566,7 +566,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
                         if (linkDoc instanceof Doc) {
                             const anchor1 = await Cast(linkDoc.anchor1, Doc);
                             const anchor2 = await Cast(linkDoc.anchor2, Doc);
-                            const currentDoc = SelectionManager.Views().length && SelectionManager.Views()[0].props.Document;
+                            const currentDoc = SelectionManager.Docs().lastElement();
                             if (currentDoc && anchor1 && anchor2) {
                                 if (Doc.AreProtosEqual(currentDoc, anchor1)) {
                                     return StrCast(anchor2.title);
-- 
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/DocumentView.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


From f20dfa857e1651ced1eb2303ea8a8462483fbc4c Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Sun, 12 Sep 2021 10:00:30 -0400
Subject: updates

---
 src/client/util/CurrentUserUtils.ts       | 51 +++++++++++++++++--------------
 src/client/views/MainView.tsx             | 20 ++++++++++++
 src/client/views/collections/TreeView.tsx |  8 ++---
 src/client/views/nodes/DocumentView.tsx   |  5 +--
 4 files changed, 55 insertions(+), 29 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 56807c63b..760949b57 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -575,11 +575,11 @@ 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: "File Manager", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' },
+            { title: "Files", 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: "Imports", target: Cast(doc.myImportDocs, 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: "Shared  with me", 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)', hidden: "IsNoviceMode()" },
         ];
@@ -807,13 +807,15 @@ export class CurrentUserUtils {
         // setup dashboards library item
         await doc.myDashboards;
         if (doc.myDashboards === undefined) {
+            const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
+            const newDashboardButton:Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
             doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], {
                 title: "My Dashboards", _showTitle: "title", _height: 400, 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", treeViewType: "fileSystem", isFolder: true, system: true
+                treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton,
+                _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
+                explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
             }));
-            const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
             // const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
             // const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
             // const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
@@ -821,7 +823,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).childContextMenuIcons = new List<string>(["plus", "user-friends", "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"]);
         }
@@ -832,7 +834,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: "Create 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",
@@ -842,6 +844,7 @@ export class CurrentUserUtils {
             }));
             (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newTrail!]);
             (doc.myTrails as any as Doc).contextMenuLabels = new List<string>(["Create New Trail"]);
+            (doc.myTrails as any as Doc).childContextMenuIcons = new List<string>(["plus"]);
         }
         return doc.myTrails as any as Doc;
     }
@@ -851,8 +854,8 @@ export class CurrentUserUtils {
         if (doc.myFilesystem === undefined) {
             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 newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
+            const newFolderButton:Doc = Docs.Create.FontIconDocument({ onClick: newFolder, _forceActive: true, toolTip: "Create 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,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
@@ -861,8 +864,9 @@ export class CurrentUserUtils {
                 _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).contextMenuLabels = new List<string>(["Create new folder"]);
+            (doc.myFilesystem as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder!]);
+            (doc.myFilesystem as any as Doc).contextMenuLabels = new List<string>(["Create new folder"]);
+            (doc.myFilesystem as any as Doc).childContextMenuIcons = new List<string>(["plus"]);
         }
         return doc.myFilesystem as any as Doc;
     }
@@ -927,7 +931,7 @@ export class CurrentUserUtils {
     static async setupSidebarButtons(doc: Doc) {
         CurrentUserUtils.setupSidebarContainer(doc);
         await CurrentUserUtils.setupToolsBtnPanel(doc);
-        CurrentUserUtils.setupUploadSidebar(doc);
+        CurrentUserUtils.setupImportSidebar(doc);
         CurrentUserUtils.setupDashboards(doc);
         CurrentUserUtils.setupPresentations(doc);
         CurrentUserUtils.setupFilesystem(doc);
@@ -1194,7 +1198,7 @@ export class CurrentUserUtils {
                     title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
                     _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
                     _chromeHidden: true, boxShadow: "0 0",
-                    explainer: "This is where documents or dashboards that other users have shared with you will appear."
+                    explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
                 }, sharingDocumentId + "outer", sharingDocumentId);
                 (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
             }
@@ -1213,13 +1217,13 @@ 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 });
-            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,
+    static setupImportSidebar(doc: Doc) {
+        if (doc.myImportDocs === undefined) {
+            const newImportButton:Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true });
+            doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
+                title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, 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: "This is where documents that are uploaded into Dash will go."
+                explainer: "This is where documents that are Imported into Dash will go."
             }));
         }
     }
@@ -1322,7 +1326,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.setupUploadSidebar(doc); // sets up the import sidebar
+        this.setupImportSidebar(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
@@ -1449,7 +1453,7 @@ export class CurrentUserUtils {
                     }
                 }
             } else if (input.files && input.files.length !== 0) {
-                const importDocs = Cast(Doc.UserDoc().myUploadDocs, Doc, null);
+                const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
                 const disposer = OverlayView.ShowSpinner();
                 DocListCastAsync(importDocs.data).then(async list => {
                     const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
@@ -1473,7 +1477,6 @@ export class CurrentUserUtils {
     }
 
     public static createNewDashboard = async (userDoc: Doc, id?: string) => {
-        const myTrails = await userDoc.myTrails as Doc;
         const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true);
         const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc;
         const dashboardCount = DocListCast(dashboards.data).length + 1;
@@ -1558,6 +1561,8 @@ Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.crea
     "creates a new dashboard when called");
 Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); },
     "creates a new presentation when called");
+Scripting.addGlobal(function createNewFolder() { return MainView.Instance.createNewFolder(); },
+    "creates a new folder in myFiles when called");
 Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
     "returns all the links to the document or its annotations", "(doc: any)");
 Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); },
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dadf5b390..809c63a45 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -63,6 +63,7 @@ import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import { ScriptField } from '../../fields/ScriptField';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -239,6 +240,25 @@ export class MainView extends React.Component {
         Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
     }
 
+    @action
+    createNewFolder = async () => {
+        if (!await this.userDoc.myFilesystem) {
+            this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
+            const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
+            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 });
+            this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], {
+                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."
+            }));
+        }
+        const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
+        Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder);
+    }
+
     getPWidth = () => this._panelWidth - this.propertiesWidth();
     getPHeight = () => this._panelHeight - (CollectionMenu.Instance?.Pinned ? 35 : 0);
     getContentsHeight = () => this._panelHeight;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8cf34ddcc..2727a0af5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -240,7 +240,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     makeFolder = () => {
-        const folder = Docs.Create.TreeDocument([], { title: "-folder-", _stayInCollection: true, isFolder: true });
+        const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
         TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
         return this.props.addDocument(folder);
     }
@@ -512,9 +512,9 @@ export class TreeView extends React.Component<TreeViewProps> {
         DocumentViewInternal.SelectAfterContextMenu = true;
     }
     contextMenuItems = () => {
-        const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" };
-        const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" };
-        const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" };
+        const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, icon: "folder-plus", label: "New Folder" };
+        const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: "copy", label: "Open Alias" };
+        const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: "eye", label: "Focus or Open" };
         return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? [makeFolder] :
             Doc.IsSystem(this.doc) ? [] :
                 this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b2629200d..ebd0211e2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -49,6 +49,7 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from "./ScriptingBox";
 import { PresBox } from './trails/PresBox';
 import React = require("react");
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
 const { Howl } = require('howler');
 
 interface Window {
@@ -152,7 +153,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     LayoutTemplate?: () => Opt<Doc>;
-    contextMenuItems?: () => { script: ScriptField, label: string }[];
+    contextMenuItems?: () => { script: ScriptField, label: string, icon: string }[];
     onClick?: () => ScriptField;
     onDoubleClick?: () => ScriptField;
     onPointerDown?: () => ScriptField;
@@ -672,7 +673,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
+            item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
 
         if (!this.props.Document.isFolder) {
             const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
-- 
cgit v1.2.3-70-g09d2


From e911344b7b93381c2dda4434480a6168291c60c0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 13 Sep 2021 14:28:37 -0400
Subject: fixed errors and warnings.

---
 src/client/util/CurrentUserUtils.ts                 |  2 +-
 src/client/views/MainView.tsx                       |  5 +++--
 src/client/views/collections/TreeView.tsx           |  2 +-
 src/client/views/nodes/DocumentView.tsx             |  2 +-
 src/client/views/nodes/button/FontIconBox.tsx       | 21 +++++----------------
 .../nodes/button/colorDropdown/ColorDropdown.tsx    | 10 ++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  2 +-
 7 files changed, 16 insertions(+), 28 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9a5ccffa0..6a7523d5b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -863,7 +863,7 @@ export class CurrentUserUtils {
                 _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.myFilesystem as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder!]);
+            (doc.myFilesystem as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder]);
             (doc.myFilesystem as any as Doc).contextMenuLabels = new List<string>(["Create new folder"]);
             (doc.myFilesystem as any as Doc).childContextMenuIcons = new List<string>(["plus"]);
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 68ec97523..9b1cc3499 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -64,6 +64,7 @@ import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
 import { ScriptField } from '../../fields/ScriptField';
+import { ButtonType } from './nodes/button/FontIconBox';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -245,9 +246,9 @@ export class MainView extends React.Component {
         if (!await this.userDoc.myFilesystem) {
             this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
             const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
-            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 });
             this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.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,
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2727a0af5..aa7b164b0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -54,7 +54,7 @@ export interface TreeViewProps {
     indentDocument?: (editTitle: boolean) => void;
     outdentDocument?: (editTitle: boolean) => void;
     ScreenToLocalTransform: () => Transform;
-    contextMenuItems: { script: ScriptField, filter: ScriptField, label: string }[];
+    contextMenuItems: { script: ScriptField, filter: ScriptField, icon: string, label: string }[];
     dontRegisterView?: boolean;
     styleProvider?: StyleProviderFunc | undefined;
     treeViewHideHeaderFields: () => boolean;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5de4c2c4e..007dca0c4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -154,7 +154,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     LayoutTemplate?: () => Opt<Doc>;
-    contextMenuItems?: () => { script: ScriptField, label: string, icon: string }[];
+    contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
     onClick?: () => ScriptField;
     onDoubleClick?: () => ScriptField;
     onPointerDown?: () => ScriptField;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fb96969c5..5ae7c3628 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -246,7 +246,6 @@ 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.Docs().lastElement();
@@ -301,7 +300,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 ? (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>
@@ -334,22 +333,14 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         const scriptCheck: string = script + "(undefined, true)";
         const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
 
-        let stroke: boolean = false;
-        let strokeIcon: any;
-        // if (script === "setStrokeColor") {
-        //     stroke = true;
-        //     const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
-        //     const width = 20 + (checkWidth / 100) * 70;
-        //     const height = 20 + (checkWidth / 100) * 70;
-        //     strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
-        // }
+        const showAlpha: boolean = script !== "setStrokeColor"; // bcz: ugh this should be changed to a field property on the button
 
         const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
             '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
             '#FFFFFF', '#f1efeb', "transparent"];
 
         const colorBox = (func: (color: ColorState) => void) => <SketchPicker
-            disableAlpha={!stroke}
+            disableAlpha={!showAlpha}
             onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
             presetColors={colorOptions} />;
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
@@ -375,10 +366,8 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
                 onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}
                 onPointerDown={e => e.stopPropagation()}>
-                {stroke ? strokeIcon : <><FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                    <div className="colorButton-color"
-                        style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }}
-                    ></div></>}
+                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+                <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />
                 {label}
                 {/* {dropdownCaret} */}
                 {this.rootDoc.dropDownOpen ?
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
index 1809f4e2e..235495250 100644
--- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
+++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
@@ -14,8 +14,7 @@ export class ColorDropdown extends Component<IButtonProps> {
         const scriptCheck: string = script + "(undefined, true)";
         const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
 
-        let stroke: boolean = false;
-        let strokeIcon: any;
+        const stroke: boolean = false;
         // if (script === "setStrokeColor") {
         //     stroke = true;
         //     const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
@@ -55,10 +54,9 @@ export class ColorDropdown extends Component<IButtonProps> {
                 style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }}
                 onClick={() => this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen}
                 onPointerDown={e => e.stopPropagation()}>
-                {stroke ? strokeIcon : <><FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
-                    <div className="colorButton-color"
-                        style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }}
-                    ></div></>}
+                <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
+                <div className="colorButton-color"
+                    style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />
                 {label}
                 {/* {dropdownCaret} */}
                 {this.props.rootDoc.dropDownOpen ?
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4b540312e..fce95547e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1160,7 +1160,7 @@ 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 && !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) })];
+            this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From d15841974305bf01251f60fa73a8464c7cb914fe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 13 Sep 2021 17:35:17 -0400
Subject: fixed dragging document that is unselected by clicking on nested
 object in sidbear to not double-drag two items effectively deleting the
 sidebar document.  fixed undoing of document decorations topbar buttons.

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

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8bc167079..6ca8dbec6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -136,7 +136,6 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         return true;
     }
 
-    @undoBatch
     onCloseClick = () => {
         const selected = SelectionManager.Views().slice();
         SelectionManager.DeselectAll();
@@ -153,8 +152,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
             return true;
         }, emptyFunction, this.onMaximizeClick, false, false);
     }
-    @undoBatch
-    @action
+
     onMaximizeClick = (e: any): void => {
         const selectedDocs = SelectionManager.Views();
         if (selectedDocs.length) {
@@ -176,7 +174,6 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         SelectionManager.DeselectAll();
     }
 
-    @undoBatch
     onIconifyClick = (): void => {
         SelectionManager.Views().forEach(dv => dv?.iconify());
         SelectionManager.DeselectAll();
@@ -433,7 +430,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
             <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
                 <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
-                    onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, click!, emptyFunction))} >
+                    onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
                     <FontAwesomeIcon icon={icon as any} />
                 </div>
             </Tooltip>);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 007dca0c4..1fcbffadd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -536,6 +536,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     onPointerMove = (e: PointerEvent): void => {
+        if (e.cancelBubble) return;
         if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return;
         if (e.cancelBubble && this.props.isDocumentActive?.()) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b1d4a79db..acc2892d8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -216,7 +216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         AnchorMenu.Instance.Status = "marquee";
 
         AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
-            !this._showSidebar && this.toggleSidebar();
+            !this.layoutDoc.showSidebar && this.toggleSidebar();
             this._sidebarRef.current?.anchorMenuClick(this.getAnchor());
         };
         AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
-- 
cgit v1.2.3-70-g09d2


From af246480e97554233293ab1dfb08b5af5e1f9d7c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 13 Sep 2021 19:38:53 -0400
Subject: fixed copyPath menu item for images/pdfs.  fixed contextmenu
 right-clicking to not require preselection of doc.

---
 src/Utils.ts                            | 10 +---------
 src/client/views/ContextMenu.tsx        | 15 +++++++++++----
 src/client/views/nodes/DocumentView.tsx | 15 +++++++++++++--
 src/client/views/nodes/ImageBox.tsx     |  2 +-
 src/client/views/nodes/PDFBox.tsx       |  5 ++---
 5 files changed, 28 insertions(+), 19 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 3fffadb6d..ddc16dceb 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -82,15 +82,7 @@ export namespace Utils {
     }
 
     export function CopyText(text: string) {
-        const textArea = document.createElement("textarea");
-        textArea.value = text;
-        document.body.appendChild(textArea);
-        textArea.focus();
-        textArea.select();
-
-        try { document.execCommand('copy'); } catch (err) { }
-
-        document.body.removeChild(textArea);
+        navigator.clipboard.writeText(text);
     }
 
     export function decimalToHexString(number: number) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index c4fabbf99..78564a11b 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -47,11 +47,17 @@ export class ContextMenu extends React.Component {
         this._mouseDown = false;
         const curX = e.clientX;
         const curY = e.clientY;
-        if (this._mouseX !== curX || this._mouseY !== curY) {
+        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
             this._shouldDisplay = false;
         }
 
-        this._shouldDisplay && (this._display = true);
+        if (this._shouldDisplay) {
+            if (this._onDisplay) {
+                this._onDisplay();
+            } else {
+                this._display = true;
+            }
+        }
     }
     componentWillUnmount() {
         document.removeEventListener("pointerdown", this.onPointerDown);
@@ -138,9 +144,9 @@ export class ContextMenu extends React.Component {
         return y;
     }
 
-
+    _onDisplay?: () => void = undefined;
     @action
-    displayMenu = (x: number, y: number, initSearch = "", showSearch = false) => {
+    displayMenu = (x: number, y: number, initSearch = "", showSearch = false, onDisplay?: () => void) => {
         //maxX and maxY will change if the UI/font size changes, but will work for any amount
         //of items added to the menu
 
@@ -149,6 +155,7 @@ export class ContextMenu extends React.Component {
         this._pageY = y;
         this._searchString = initSearch;
         this._shouldDisplay = true;
+        this._onDisplay = onDisplay;
     }
 
     @action
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1fcbffadd..193befa5d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { AudioField } from "../../../fields/URLField";
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -671,6 +671,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const cm = ContextMenu.Instance;
         if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
 
+        if (e?.buttons === 2) {
+            const onDisplay = () => setTimeout(() => {
+                DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                setTimeout(() => {
+                    const ele = document.elementFromPoint(e.clientX, e.clientY);
+                    simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
+                });
+            });
+            cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
+            return;
+        }
+
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
@@ -777,7 +789,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
-        DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
     }
 
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 38deb4a73..b41bfd3ea 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -144,9 +144,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? "Dynamic Res" : "Full Res"}`, event: this.resolution, icon: "expand-arrows-alt" });
+            funcs.push({ description: "Copy path", event: () => Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" });
             if (!Doc.UserDoc().noviceMode) {
                 funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
-                funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
 
                 const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
                 const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88fb2ed13..ce851b830 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -226,9 +226,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(this.pdfUrl.url.pathname), icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+        funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" });
+        //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
-- 
cgit v1.2.3-70-g09d2


From 7ab2011d3b6bb7bb2e945265d4ff97983a5a1f38 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 14 Sep 2021 19:13:34 -0400
Subject: fixed right-click context menu for Windows.

---
 src/Utils.ts                            | 30 +++++++++++++++++-------------
 src/client/views/nodes/DocumentView.tsx |  9 +++++++--
 2 files changed, 24 insertions(+), 15 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index ddc16dceb..6eacd8296 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -552,19 +552,23 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
             screenY: sy,
         })));
 
-    rightClick && element.dispatchEvent(
-        new MouseEvent("contextmenu", {
-            view: window,
-            bubbles: true,
-            cancelable: true,
-            button: 2,
-            clientX: x,
-            clientY: y,
-            movementX: 0,
-            movementY: 0,
-            screenX: sx,
-            screenY: sy,
-        }));
+    if (rightClick) {
+        const me =
+            new MouseEvent("contextmenu", {
+                view: window,
+                bubbles: true,
+                cancelable: true,
+                button: 2,
+                clientX: x,
+                clientY: y,
+                movementX: 0,
+                movementY: 0,
+                screenX: sx,
+                screenY: sy,
+            });
+        (me as any).dash = true;
+        element.dispatchEvent(me);
+    }
 }
 
 export function lightOrDark(color: any) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5ef49bd3b..b680fcf3b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -672,7 +672,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const cm = ContextMenu.Instance;
         if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
 
-        if (e?.buttons === 2) {
+        if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => setTimeout(() => {
                 DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => {
@@ -680,7 +680,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
                 });
             });
-            cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
+            if (navigator.userAgent.includes("Macintosh")) {
+                cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
+            }
+            else {
+                onDisplay();
+            }
             return;
         }
 
-- 
cgit v1.2.3-70-g09d2


From a6c7dc12a4a6b2c51c0363876e892a1adb0b382a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 15 Sep 2021 10:02:55 -0400
Subject: fixed dragging so that only isDocumentActive documents are dragged
 (get move events)  which fixes a bug where a nested document and its
 container might both be dragged at the same time.

---
 src/client/util/DragManager.ts                            |  4 ++--
 src/client/views/DocumentDecorations.tsx                  |  2 +-
 src/client/views/nodes/DocumentView.tsx                   | 12 ++++++------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  8 +++++---
 4 files changed, 14 insertions(+), 12 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f7ef9ae6f..421e4c6bb 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -145,7 +145,7 @@ export namespace DragManager {
         removeDropProperties?: string[];
         moveDocument?: MoveFunction;
         removeDocument?: RemoveFunction;
-        isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
+        isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
     }
     export class LinkDragData {
         constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc,) {
@@ -225,7 +225,7 @@ export namespace DragManager {
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
                 docDragData.droppedDocuments =
-                    await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
+                    await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
                         docDragData.dropAction === "alias" ? Doc.MakeAlias(d) :
                             docDragData.dropAction === "proto" ? Doc.GetProto(d) :
                                 docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d));
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5ffdb8ea1..e70ce4664 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -122,7 +122,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
         dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView.props.moveDocument;
-        dragData.isSelectionMove = true;
+        dragData.isDocDecorationMove = true;
         dragData.canEmbed = dragTitle;
         this._hidden = this.Interacting = true;
         DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b680fcf3b..e8a78d75c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -529,9 +529,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
             }
-            document.removeEventListener("pointermove", this.onPointerMove);
+            if (this.props.isDocumentActive?.()) {
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.addEventListener("pointermove", this.onPointerMove);
+            }
             document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
         }
     }
@@ -539,10 +541,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onPointerMove = (e: PointerEvent): void => {
         if (e.cancelBubble) return;
         if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return;
-        if (e.cancelBubble && this.props.isDocumentActive?.()) {
-            document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
-        }
-        else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+
+        if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index acc2892d8..caca215e5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -435,10 +435,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     }
     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));
+        this.ProseRef = ele;
+        if (ele) {
+            this.setupEditor(this.config, this.props.fieldKey);
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
+        }
         // if (this.autoHeight) this.tryUpdateScrollHeight(); 
     }
 
-- 
cgit v1.2.3-70-g09d2


From 64119b5d8766725025b8b2bfda72f2401bba0f00 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 16 Sep 2021 21:04:57 -0400
Subject: added search() component method.  changed search menu to call search
 on documents that match search string.  added seach bar for web pages.

---
 src/client/views/GlobalKeyHandler.ts               | 16 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/PDFBox.tsx                  | 18 ++++-
 src/client/views/nodes/WebBox.scss                 | 86 ++++++++++++++++++++++
 src/client/views/nodes/WebBox.tsx                  | 65 +++++++++++-----
 src/client/views/pdf/PDFViewer.tsx                 | 13 +---
 src/client/views/search/SearchBox.tsx              | 15 ++--
 8 files changed, 172 insertions(+), 46 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index f66c9c788..de6f4ae8b 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -10,10 +10,11 @@ import { Cast, PromiseValue } from "../../fields/Types";
 import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
 import { DocServer } from "../DocServer";
 import { DocumentType } from "../documents/DocumentTypes";
-import { DictationManager } from "../util/DictationManager";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { DragManager } from "../util/DragManager";
 import { GroupManager } from "../util/GroupManager";
 import { SelectionManager } from "../util/SelectionManager";
+import { SettingsManager } from "../util/SettingsManager";
 import { SharingManager } from "../util/SharingManager";
 import { SnappingManager } from "../util/SnappingManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
@@ -27,8 +28,6 @@ import { LightboxView } from "./LightboxView";
 import { MainView } from "./MainView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import { AnchorMenu } from "./pdf/AnchorMenu";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { SettingsManager } from "../util/SettingsManager";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -222,10 +221,13 @@ export class KeyManager {
                 PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 break;
             case "f":
-                const searchBtn = Doc.UserDoc().searchBtn as Doc;
-
-                if (searchBtn) {
-                    MainView.Instance.selectMenu(searchBtn);
+                if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) {
+                    SelectionManager.Views()[0].ComponentView?.search?.("", false, false);
+                } else {
+                    const searchBtn = Doc.UserDoc().searchBtn as Doc;
+                    if (searchBtn) {
+                        MainView.Instance.selectMenu(searchBtn);
+                    }
                 }
                 break;
             case "o":
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index be0b078ec..94cf1c5a6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -902,7 +902,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 HistoryUtil.pushState(state);
             }
         }
-        SelectionManager.DeselectAll();
+        if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
+            SelectionManager.DeselectAll();
+        }
         if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
             this.props.focus(doc, options);
         } else {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e8a78d75c..187905960 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -96,6 +96,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getScrollHeight?: () => number;
+    search?: (str:string, bwd?:boolean, clear?:boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index ce851b830..274d166f1 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -96,7 +96,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
     }
 
-    public search = (string: string, fwd: boolean) => this._pdfViewer?.search(string, fwd);
+    public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
+        if (!this._searching && !clear) {
+            this._searching = true;
+            setTimeout(() => {
+                this._searchRef.current?.focus();
+                this._searchRef.current?.select();
+                this._searchRef.current?.setRangeText(searchString);
+            });
+        }
+        return this._pdfViewer?.search(searchString, bwd, clear) || false;
+    });
     public prevAnnotation = () => this._pdfViewer?.prevAnnotation();
     public nextAnnotation = () => this._pdfViewer?.nextAnnotation();
     public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; };
@@ -184,8 +194,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <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}
-                        onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
-                    <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
+                        onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+                    <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" />
                     </button>
                     <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation} >
@@ -196,7 +206,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     </button>
                 </div>
                 <button className="pdfBox-overlayButton" title={searchTitle}
-                    onClick={action(() => { this._searching = !this._searching; this.search("mxytzlaf", true); })} >
+                    onClick={action(() => { this._searching = !this._searching; this.search("", true, true); })} >
                     <div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} />
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" />
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 417a17d96..79289abaa 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -6,6 +6,92 @@
     position: relative;
     display: flex;
 
+    .webBox-ui {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        z-index: 1;
+        pointer-events: none;
+        top: 0;
+        left: 0;
+        overflow: hidden;
+
+        .webBox-overlayButton {
+            border-bottom-left-radius: 50%;
+            display: flex;
+            justify-content: space-evenly;
+            align-items: center;
+            height: 20px;
+            background: none;
+            padding: 0;
+            position: absolute;
+            pointer-events: all;
+            color: white;
+            bottom: 0;
+            right: 0;
+
+            .webBox-overlayButton-arrow {
+                width: 0;
+                height: 0;
+                border-top: 10px solid transparent;
+                border-bottom: 10px solid transparent;
+                border-right: 15px solid #121721;
+                transition: all 0.5s;
+            }
+
+            .webBox-overlayButton-iconCont {
+                background: #121721;
+                height: 20px;
+                width: 25px;
+                display: flex;
+                position: relative;
+                align-items: center;
+                justify-content: center;
+                border-radius: 3px;
+                pointer-events: all;
+            }
+        }
+
+        .webBox-nextIcon,
+        .webBox-prevIcon {
+            background: #121721;
+            color: white;
+            height: 20px;
+            width: 25px;
+            display: flex;
+            position: relative;
+            align-items: center;
+            justify-content: center;
+            border-radius: 3px;
+            pointer-events: all;
+            padding: 0px;
+        }
+
+        .webBox-overlayButton:hover {
+            background: none;
+        }
+
+
+        .webBox-overlayCont {
+            position: absolute;
+            width: calc(100% - 40px);
+            height: 20px;
+            background: #121721;
+            bottom: 0;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            overflow: hidden;
+            transition: left .5s;
+            pointer-events: all;
+
+            .webBox-searchBar {
+                width: 70%;
+                font-size: 14px;
+            }
+        }
+    }
+
     .webBox-overlayButton-sidebar {
         background: #121721;
         height: 25px;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 0c9c36418..cb9256595 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -16,6 +16,7 @@ import { TraceMobx } from "../../../fields/util";
 import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { KeyCodes } from "../../util/KeyCodes";
 import { Scripting } from "../../util/Scripting";
 import { SnappingManager } from "../../util/SnappingManager";
 import { undoBatch } from "../../util/UndoManager";
@@ -52,6 +53,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _keyInput = React.createRef<HTMLInputElement>();
     private _initialScroll: Opt<number>;
     private _sidebarRef = React.createRef<SidebarAnnos>();
+    private _searchRef = React.createRef<HTMLInputElement>();
+    private _searchString = "";
+    @observable private _searching: boolean = false;
+    @observable _showSidebar = false;
     @observable private _scrollTimer: any;
     @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private _marqueeing: number[] | undefined;
@@ -73,6 +78,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
     }
 
+    @action
+    search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+        if (!this._searching && !clear) {
+            this._searching = true;
+            setTimeout(() => {
+                this._searchRef.current?.focus();
+                this._searchRef.current?.select();
+                this._searchRef.current?.setRangeText(searchString);
+            });
+        }
+        if (clear) {
+            this._iframe?.contentWindow?.getSelection()?.empty();
+        }
+        if (searchString) {
+            (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
+        }
+        return true;
+    }
     async componentDidMount() {
         this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
 
@@ -524,9 +547,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             </div>;
 
     }
-    @observable _showSidebar = false;
     @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
 
+    @computed get searchUI() {
+        return <div className="webBox-ui"
+            onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
+            <div className="webBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
+                <button className="webBox-overlayButton" title={"search"} />
+                <input className="webBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
+                    onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+                <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+                    <FontAwesomeIcon icon="search" size="sm" />
+                </button>
+            </div>
+            <button className="webBox-overlayButton" title={"search"}
+                onClick={action(() => { this._searching = !this._searching; this.search("", false, true); })} >
+                <div className="webBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} />
+                <div className="webBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+                    <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" />
+                </div>
+            </button>
+        </div>;
+    }
+    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
     showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
     panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
@@ -623,25 +666,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={this.sidebarBtnDown} >
                     <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
                 </div>
-                {/* <div className="webBox-search" key="search" title="Search"
-                    style={{
-                        top: 10,
-                        left: 10,
-                        width: 200,
-                        height: 50,
-                        display: "block",
-                        position: "absolute"
-                    }}
-                >
-                    <input onKeyPress={e => {
-                        if (e.key === "Enter") {
-                            (this._iframe?.contentWindow as any)?.find(e.target.value);
-                        }
-                    }} onChange={e => {
-                        this._iframe?.contentWindow?.getSelection()?.empty();
-                        (this._iframe?.contentWindow as any)?.find(e.target.value)
-                    }}></input>
-                </div> */}
+                {!this.props.isContentActive() ? (null) : this.searchUI}
             </div>);
     }
 }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d953c6b6c..7aa18e46f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -125,12 +125,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             });
 
-        this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.props.rootDoc),
-            m => {
-                if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0);
-                else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200);
-            }, { fireImmediately: true });
-
         this._disposers.selected = reaction(() => this.props.isSelected(),
             selected => {
                 // if (!selected) {
@@ -337,10 +331,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @action
-    search = (searchString: string, fwd: boolean, clear: boolean = false) => {
+    search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
         const findOpts = {
             caseSensitive: false,
-            findPrevious: !fwd,
+            findPrevious: bwd,
             highlightAll: true,
             phraseSearch: true,
             query: searchString
@@ -348,7 +342,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (clear) {
             this._pdfViewer?.findController.executeCommand('reset', { query: "" });
         } else if (!searchString) {
-            fwd ? this.nextAnnotation() : this.prevAnnotation();
+            bwd ? this.prevAnnotation() : this.nextAnnotation();
         } else if (this._pdfViewer?.pageViewsReady) {
             this._pdfViewer.findController.executeCommand('findagain', findOpts);
         }
@@ -357,6 +351,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             this._mainCont.current.addEventListener("pagesloaded", executeFind);
             this._mainCont.current.addEventListener("pagerendered", executeFind);
         }
+        return true;
     }
 
     @action
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9c353e9d0..3612bd7c4 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -104,9 +104,9 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
      * This method is called when the user clicks on a search result. The _selectedResult is
      * updated accordingly and the doc is highlighted with the selectElement method.
      */
-    onResultClick = action((doc: Doc) => {
-        this.selectElement(doc);
+    onResultClick = action(async (doc: Doc) => {
         this._selectedResult = doc;
+        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
     });
 
     makeLink = action((linkTo: Doc) => {
@@ -269,8 +269,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
      * This method selects a doc by either jumping to it (centering/zooming in on it)
      * or opening it in a new tab.
      */
-    selectElement = async (doc: Doc) => {
-        await DocumentManager.Instance.jumpToDocument(doc, true);
+    selectElement = async (doc: Doc, finishFunc: () => void) => {
+        await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc);
     }
 
     /**
@@ -307,7 +307,12 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
                 validResults++;
                 return (
                     <Tooltip key={result[0][Id]} placement={"right"} title={<><div className="dash-tooltip">{title}</div></>}>
-                        <div onClick={isLinkSearch ? () => this.makeLink(result[0]) : () => this.onResultClick(result[0])} className={className}>
+                        <div onClick={isLinkSearch ?
+                            () => this.makeLink(result[0]) :
+                            e => {
+                                this.onResultClick(result[0]);
+                                e.stopPropagation();
+                            }} className={className}>
                             <div className="searchBox-result-title">
                                 {title}
                             </div>
-- 
cgit v1.2.3-70-g09d2


From a75ccf3bd6c0029116d46436bd5a9d956b6e04e7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Sep 2021 04:13:34 -0400
Subject: fixed not inadvertently moving webBoxes after creating a marquee adn
 then clicking in the webBox, then clicking outside it.

---
 src/client/views/AntimodeMenu.tsx       | 2 +-
 src/client/views/nodes/DocumentView.tsx | 8 ++++++--
 src/client/views/nodes/WebBox.tsx       | 7 ++++---
 3 files changed, 11 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index fe6d39ca4..0f1fc6b69 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -16,7 +16,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
 
     @observable protected _top: number = -300;
     @observable protected _left: number = -300;
-    @observable protected _opacity: number = 1;
+    @observable protected _opacity: number = 0;
     @observable protected _transitionProperty: string = "opacity";
     @observable protected _transitionDuration: string = "0.5s";
     @observable protected _transitionDelay: string = "";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 187905960..46a8b6629 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -96,7 +96,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getScrollHeight?: () => number;
-    search?: (str:string, bwd?:boolean, clear?:boolean) => boolean;
+    search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -556,10 +556,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    onPointerUp = (e: PointerEvent): void => {
+    cleanupPointerEvents = () => {
         this.cleanUpInteractions();
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
+    }
+
+    onPointerUp = (e: PointerEvent): void => {
+        this.cleanupPointerEvents();
 
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ca1b0d8ac..6a20ca14a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -152,6 +152,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+        this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
     }
 
     @action
@@ -215,8 +216,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     iframeUp = (e: PointerEvent) => {
+        this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
-            this._iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
             const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
             const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
             const sel = this._iframe.contentWindow.getSelection();
@@ -237,7 +238,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
         e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
         if (word) {
-            this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
             setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
         } else {
             this._iframeClick = this._iframe ?? undefined;
@@ -267,6 +267,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     iframeLoaded = (e: any) => {
         const iframe = this._iframe;
+        this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
         if (iframe?.contentDocument) {
             iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
             this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
@@ -457,7 +458,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this._setPreviewCursor?.(x, y, false, false);
             ContextMenu.Instance.closeMenu();
             ContextMenu.Instance.setIgnoreEvents(false);
-            if (e?.button === 2 || e?.ctrlKey) {
+            if (e?.button === 2 || e?.altKey) {
                 this.specificContextMenu(undefined as any);
                 this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
             }
-- 
cgit v1.2.3-70-g09d2


From f097eefd91614d4efa2a89b3d8c7d1071d6c0b6f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Sep 2021 14:07:09 -0400
Subject: added 'unset' docFilters.  changed link doc views to use comparison
 box with title/caption.  fixed linkEditor to write to data doc. generalized
 comparisonBox rendering to use parameterized fields. fixed pdf/web to honor
 pointerEvents none prop and fixed textAnnotations to get rendered once as an
 Annotation. moved filterIcon stuff into DocumentView

---
 src/Utils.ts                                          |  4 +++-
 src/client/DocServer.ts                               |  2 ++
 src/client/documents/Documents.ts                     | 17 +++++++++++------
 src/client/views/collections/CollectionView.tsx       | 12 ------------
 .../collectionFreeForm/CollectionFreeFormLinkView.tsx |  6 +++---
 src/client/views/linking/LinkEditor.tsx               | 11 ++++++-----
 src/client/views/nodes/ComparisonBox.tsx              |  8 +++++---
 src/client/views/nodes/DocumentView.tsx               | 19 +++++++++++++++++--
 src/client/views/nodes/LinkBox.tsx                    | 19 ++++++-------------
 src/client/views/nodes/LinkDescriptionPopup.tsx       |  7 ++++---
 src/client/views/nodes/PDFBox.tsx                     |  9 ++++-----
 src/client/views/nodes/WebBox.tsx                     | 14 ++++++++------
 src/client/views/pdf/Annotation.scss                  |  3 +++
 src/client/views/pdf/PDFViewer.tsx                    | 13 +++++++------
 src/fields/Doc.ts                                     |  2 +-
 15 files changed, 80 insertions(+), 66 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 6eacd8296..e11f1154e 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -125,7 +125,9 @@ export namespace Utils {
         // bcz: isTransparent(__value__) is a hack.  it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
         return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`;// bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
     }
-
+    export function PropUnsetFilter(prop: string) {
+        return `${prop}:any,${noRecursionHack}:unset`;
+    }
 
     export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) {
         return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")";
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e498a7cca..3b376a0e7 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -241,6 +241,7 @@ export namespace DocServer {
             // the field has been returned from the server
             const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
 
+            console.log(id)
             // when the serialized RefField has been received, go head and begin deserializing it into an object.
             // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
             // future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
@@ -264,6 +265,7 @@ export namespace DocServer {
                 } else {
                     delete _cache[id];
                 }
+                console.log(id, field);
                 return field;
                 // either way, overwrite or delete any promises cached at this id (that we inserted as flags
                 // to indicate that the field was in the process of being fetched). Now everything
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f50f306a3..e8185400e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -962,13 +962,17 @@ export namespace DocUtils {
                 // metadata facets that exist
                 const exists = Object.keys(facet).filter(value => facet[value] === "exists");
 
+                // metadata facets that exist
+                const unsets = Object.keys(facet).filter(value => facet[value] === "unset");
+
                 // facets that have an x next to them
                 const xs = Object.keys(facet).filter(value => facet[value] === "x");
 
-                if (!exists.length && !xs.length && !checks.length && !matches.length) return true;
+                if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
                 const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
                 const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
                 const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined);
+                const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
                 const satisfiesMatchFacets = !matches.length ? true : matches.some(value => {
                     if (facetKey.startsWith("*")) { //  fields starting with a '*' are used to match families of related fields.  ie, *lastModified will match text-lastModified, data-lastModified, etc
                         const allKeys = Array.from(Object.keys(d));
@@ -980,11 +984,11 @@ export namespace DocUtils {
                 });
                 // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
                 if ((parentCollection?.currentFilter as Doc)?.filterBoolean === "OR") {
-                    if (satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+                    if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
                 }
                 // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
                 else {
-                    if (!satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+                    if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
                 }
 
             }
@@ -1088,10 +1092,11 @@ export namespace DocUtils {
             "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
             "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,
+            linkDisplay: true,
+            hidden: true,
             linkRelationship,
-            _layoutKey: "layout_linkView",
+            _showCaption: "description",
+            _showTitle: "linkRelationship",
             description
         }, id), showPopup);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5c9c8063b..38e027fb3 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -246,12 +246,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
     @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
 
-    /**
-    * 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();
-    }
 
     @observable _subView: any = undefined;
 
@@ -280,12 +274,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
             {this.showIsTagged()}
             {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
-            {this.showFilterIcon ?
-                <FontAwesomeIcon icon={"filter"} size="lg"
-                    style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
-                    onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
-                />
-                : (null)}
         </div>);
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3b3e069d8..2cb487588 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
+import { Doc, Field } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
 import { List } from "../../../../fields/List";
 import { NumCast, StrCast } from "../../../../fields/Types";
@@ -180,7 +180,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (!this.renderData) return (null);
         const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
         LinkManager.currentLink = this.props.LinkDocs[0];
-        const linkRelationship = StrCast(LinkManager.currentLink?.linkRelationship); //get string representing relationship
+        const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship
         const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
         const linkColorList = Doc.UserDoc().linkColorList as List<string>;
         //access stroke color using index of the relationship in the color list (default black)
@@ -189,7 +189,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
                 d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
             {textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
-                {StrCast(this.props.LinkDocs[0].description)}
+                {Field.toString(this.props.LinkDocs[0].description as any as Field)}
             </text>}
         </>);
     }
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 240a71c3e..219f7d3a2 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -2,13 +2,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, StrListCast } from "../../../fields/Doc";
-import { DateCast, StrCast } from "../../../fields/Types";
+import { Doc, StrListCast, Field } from "../../../fields/Doc";
+import { DateCast, StrCast, Cast } from "../../../fields/Types";
 import { LinkManager } from "../../util/LinkManager";
 import { undoBatch } from "../../util/UndoManager";
 import './LinkEditor.scss';
 import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
 import React = require("react");
+import { ToString } from "../../../fields/FieldSymbols";
 
 
 interface LinkEditorProps {
@@ -20,7 +21,7 @@ interface LinkEditorProps {
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
-    @observable description = StrCast(LinkManager.currentLink?.description);
+    @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
     @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
     @observable openDropdown: boolean = false;
     @observable showInfo: boolean = false;
@@ -41,7 +42,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @undoBatch
     setRelationshipValue = action((value: string) => {
         if (LinkManager.currentLink) {
-            LinkManager.currentLink.linkRelationship = value;
+            Doc.GetProto(LinkManager.currentLink).linkRelationship = value;
             const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
             const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
             // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color 
@@ -79,7 +80,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @undoBatch
     setDescripValue = action((value: string) => {
         if (LinkManager.currentLink) {
-            LinkManager.currentLink.description = value;
+            Doc.GetProto(LinkManager.currentLink).description = value;
             this.buttonColor = "rgb(62, 133, 55)";
             setTimeout(action(() => this.buttonColor = ""), 750);
             return true;
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 6708a08ee..72c7e4f45 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -15,6 +15,7 @@ import "./ComparisonBox.scss";
 import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import React = require("react");
+import { DocumentType } from '../../documents/DocumentTypes';
 
 export const comparisonSchema = createSchema({});
 
@@ -87,7 +88,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             </div>;
         };
         const displayDoc = (which: string) => {
-            const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
+            var whichDoc = Cast(this.dataDoc[which], Doc, null);
+            if (whichDoc.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
             return whichDoc ? <>
                 <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     isContentActive={returnFalse}
@@ -112,9 +114,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
         return (
             <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)}
+                {displayBox(this.fieldKey === "data" ? "compareBox-after" : `${this.fieldKey}2`, 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)}
+                    {displayBox(this.fieldKey === "data" ? "compareBox-before" : `${this.fieldKey}1`, 0, 0)}
                 </div>
 
                 <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)`, cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? "e-resize" : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 46a8b6629..35249c7f4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { AudioField } from "../../../fields/URLField";
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick, returnEmptyString } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -802,6 +802,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
     }
 
+    collectionFilters = () => StrListCast(this.props.Document._docFilters);
+    collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
+    @computed get showFilterIcon() {
+        return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
+            this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
+    }
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
@@ -832,7 +838,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none",
+                pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (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}
@@ -869,6 +875,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         switch (property) {
+            case StyleProp.ShowTitle: return "";
             case StyleProp.PointerEvents: return "none";
             case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
             default: return this.props.styleProvider?.(doc, props, property);
@@ -901,6 +908,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
+                    showTitle={returnEmptyString}
+                    hideCaptions={true}
                     fitWidth={returnTrue}
                     styleProvider={this.anchorStyleProvider}
                     removeDocument={this.hideLinkAnchor}
@@ -1087,6 +1096,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     </div>
                 </>
             }
+            {this.showFilterIcon ?
+                <FontAwesomeIcon icon={"filter"} size="lg"
+                    style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
+                    onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
+                />
+                : (null)}
         </div>;
     }
 }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 55ea45bb8..b82d16677 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,10 +2,10 @@ import React = require("react");
 import { observer } from "mobx-react";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { makeInterface } from "../../../fields/Schema";
-import { returnFalse } from "../../../Utils";
-import { CollectionTreeView } from "../collections/CollectionTreeView";
+import { emptyFunction, returnFalse } from "../../../Utils";
 import { ViewBoxBaseComponent } from "../DocComponent";
 import { StyleProp } from "../StyleProvider";
+import { ComparisonBox } from "./ComparisonBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./LinkBox.scss";
 
@@ -20,22 +20,15 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
         if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true);
         return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`}
             style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} >
-            <CollectionTreeView {...this.props}
-                childDocuments={[this.dataDoc]}
-                treeViewOpen={true}
-                treeViewExpandedView={"fields"}
-                treeViewHideTitle={true}
-                treeViewSkipFields={["treeViewExpandedView", "aliases", "_removeDropProperties",
-                    "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]}
+            <ComparisonBox {...this.props}
+                fieldKey="anchor"
+                setHeight={emptyFunction}
                 dontRegisterView={true}
                 renderDepth={this.props.renderDepth + 1}
-                CollectionView={undefined}
-                isAnyChildContentActive={returnFalse}
                 isContentActive={this.isContentActiveFunc}
                 addDocument={returnFalse}
                 removeDocument={returnFalse}
-                moveDocument={returnFalse}>
-            </CollectionTreeView>
+                moveDocument={returnFalse} />
         </div>;
     }
 } 
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index b62a4dd56..a9d33f161 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,8 +1,9 @@
 import React = require("react");
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import "./LinkDescriptionPopup.scss";
-import { observable, action } from "mobx";
+import { Doc } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
+import "./LinkDescriptionPopup.scss";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 
 
@@ -25,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onDismiss = (add: boolean) => {
         LinkDescriptionPopup.descriptionPopup = false;
         if (add) {
-            LinkManager.currentLink && (LinkManager.currentLink.description = this.description);
+            LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description);
         }
     }
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f0a502e31..30b4dc92a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,9 +3,9 @@ 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, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, StrListCast } from "../../../fields/Doc";
 import { documentSchema } from '../../../fields/documentSchemas';
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
@@ -25,6 +25,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -249,9 +250,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable _showSidebar = false;
     @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
 
-    contentScaling = () => {
-        return 1;
-    }
+    contentScaling = () => 1;
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6a20ca14a..60a83d612 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -532,7 +532,8 @@ 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.props.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+        return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
             style={{ width: !this.layoutDoc.allowReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
             {this.urlContent}
         </div>;
@@ -575,10 +576,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+    transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()];
+    opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()];
     render() {
-        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const scale = previewScale * (this.props.scaling?.() || 1);
         const renderAnnotations = (docFilters?: () => string[]) =>
@@ -593,7 +595,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ScreenToLocalTransform={this.scrollXf}
                 scaling={returnOne}
                 dropAction={"alias"}
-                docFilters={docFilters || this.props.docFilters}
+                docFilters={docFilters || this.basicFilter}
                 dontRenderDocuments={docFilters ? false : true}
                 select={emptyFunction}
                 ContentScaling={returnOne}
@@ -606,7 +608,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return (
             <div className="webBox" ref={this._mainCont}
-                style={{ pointerEvents: this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none" }} >
+                style={{ pointerEvents: this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none" }} >
                 <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
                     <base target="_blank" />
                     <div className={"webBox-outerContent"} ref={this._outerRef}
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
index e98f071c3..1de60ffed 100644
--- a/src/client/views/pdf/Annotation.scss
+++ b/src/client/views/pdf/Annotation.scss
@@ -4,4 +4,7 @@
     position: absolute;
     background-color: rgba(146, 245, 95, 0.467);
     transition: opacity 0.5s;
+    &:hover {
+        cursor: pointer;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7aa18e46f..78adedf5b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -179,7 +179,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
+            const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
             if (scrollTo !== undefined) {
                 focusSpeed = 500;
 
@@ -502,8 +502,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
     panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+    transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()];
+    opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()];
     @computed get overlayLayer() {
         const renderAnnotations = (docFilters?: () => string[]) =>
             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
@@ -516,7 +517,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 select={emptyFunction}
                 ContentScaling={this.contentZoom}
                 bringToFront={emptyFunction}
-                docFilters={docFilters || this.props.docFilters}
+                docFilters={docFilters || this.basicFilter}
                 dontRenderDocuments={docFilters ? false : true}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
@@ -543,7 +544,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </div>;
     }
     @computed get pdfViewerDiv() {
-        return <div className={"pdfViewerDash-text" + (this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />;
+        return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />;
     }
     @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
     @computed get standinViews() {
@@ -556,7 +557,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     render() {
         TraceMobx();
         return <div className="pdfViewer-content">
-            <div className={`pdfViewerDash${this.props.isContentActive() ? "-interactive" : ""}`} ref={this._mainCont}
+            <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
                 onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 style={{
                     overflowX: this._zoomed !== 1 ? "scroll" : undefined,
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4d040f3bc..57bd0f46f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1136,7 +1136,7 @@ export namespace Doc {
     // filters document in a container collection:
     // all documents with the specified value for the specified key are included/excluded
     // based on the modifiers :"check", "x", undefined
-    export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+    export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
         if (!container) return;
         const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
         const docFilters = Cast(container[filterField], listSpec("string"), []);
-- 
cgit v1.2.3-70-g09d2


From 50886bf908a5a155849daa5920b42a86752f9d1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Sep 2021 15:09:55 -0400
Subject: fixed warnings

---
 src/client/DocServer.ts                                                | 2 --
 src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                                | 2 +-
 3 files changed, 2 insertions(+), 4 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 3b376a0e7..e498a7cca 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -241,7 +241,6 @@ export namespace DocServer {
             // the field has been returned from the server
             const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
 
-            console.log(id)
             // when the serialized RefField has been received, go head and begin deserializing it into an object.
             // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
             // future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
@@ -265,7 +264,6 @@ export namespace DocServer {
                 } else {
                     delete _cache[id];
                 }
-                console.log(id, field);
                 return field;
                 // either way, overwrite or delete any promises cached at this id (that we inserted as flags
                 // to indicate that the field was in the process of being fetched). Now everything
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index ecff01e67..71642216c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -16,7 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     public showMarquee: () => void = unimplementedFunction;
     public hideMarquee: () => void = unimplementedFunction;
     public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
-    public isShown = () => { return this._opacity > 0; }
+    public isShown = () => this._opacity > 0;
     constructor(props: Readonly<{}>) {
         super(props);
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 35249c7f4..d2b4b0348 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -806,7 +806,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
     @computed get showFilterIcon() {
         return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
-            this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
+            this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
     }
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
-- 
cgit v1.2.3-70-g09d2


From 240f2c9bda4a9a32d84d1de93820f6fbc8eef458 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 Sep 2021 12:24:34 -0400
Subject: fixed equations/ink to work properly when not fitwidth in lightbox. 
 don't show titles for ink or equations using fields now.

---
 src/client/documents/Documents.ts                  |  8 +++++---
 src/client/views/DocumentDecorations.tsx           | 20 ++++++++++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/EquationBox.scss            |  3 +++
 src/client/views/nodes/EquationBox.tsx             |  7 ++++++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 24 ++++++++++------------
 6 files changed, 37 insertions(+), 27 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e8185400e..fafbc4a7d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -430,7 +430,7 @@ export namespace Docs {
             }],
             [DocumentType.EQUATION, {
                 layout: { view: EquationBox, dataField: defaultDataKey },
-                options: { links: ComputedField.MakeFunction("links(self)") as any }
+                options: { links: ComputedField.MakeFunction("links(self)") as any, hideResizeHandles: true, hideDecorationTitle: true }
             }],
             [DocumentType.FUNCPLOT, {
                 layout: { view: FunctionPlotBox, dataField: defaultDataKey },
@@ -463,9 +463,9 @@ export namespace Docs {
                 layout: { view: CollectionView, dataField: defaultDataKey },
                 options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
             }],
-            [DocumentType.INK, {
+            [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
                 layout: { view: InkingStroke, dataField: defaultDataKey },
-                options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any }
+                options: { links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SCREENSHOT, {
                 layout: { view: ScreenshotBox, dataField: defaultDataKey },
@@ -716,6 +716,8 @@ export namespace Docs {
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
+            I.hideDecorationTitle = true;  // don't show title when selected
+            I.hideOpenButton = true; // don't show open full screen button when selected
             I.fillColor = fillColor;
             I.strokeWidth = strokeWidth;
             I.strokeBezier = strokeBezier;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3d6f157b6..d785d5419 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -388,10 +388,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 Doc.GetProto(doc).data = new InkField(inkPts.map(ipt =>  // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                ({
-                    X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
-                    Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
-                })));
+                    ({
+                        X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+                        Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+                    })));
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
@@ -421,7 +421,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
-        const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup);
+        const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles;
+        const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
+        const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
         const canDelete = SelectionManager.Views().some(docView => {
             const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
             return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
@@ -473,12 +475,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
                 }}>
                     {!canDelete ? <div /> : topBtn("close", "times", undefined, this.onCloseClick, "Close")}
-                    {seldoc.props.hideDecorationTitle || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : titleArea}
-                    {seldoc.props.hideResizeHandles || seldoc.props.Document.type === DocumentType.EQUATION ? (null) :
+                    {hideTitle ? (null) : titleArea}{!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
+
+                    {hideResizers ? (null) :
                         <>
-                            {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
+                            {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
                                 topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
-                            {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
                             <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
                             <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
                             <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d2b4b0348..60ceac007 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1168,7 +1168,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
-        if (this.docView.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
+        if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
             if (docuBox.length) return docuBox[0].getBoundingClientRect();
         }
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index e69de29bb..6c9d53d10 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -0,0 +1,3 @@
+.equationBox-cont {
+    transform-origin: top left;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 11ef6562f..f1f802c13 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -84,8 +84,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDo
     }
     render() {
         TraceMobx();
-        return (<div onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
+        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        return (<div className="equationBox-cont"
+            onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
             style={{
+                transform: `scale(${scale})`,
+                width: `${100 / scale}%`,
+                height: `${100 / scale}%`,
                 pointerEvents: !this.props.isSelected() ? "none" : undefined,
             }}
             onKeyDown={e => e.stopPropagation()}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3e5c4456c..ebd509669 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1469,19 +1469,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
     tryUpdateScrollHeight = () => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-            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);
-                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 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);
+            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...
                 }
             }
         }
-- 
cgit v1.2.3-70-g09d2


From b65157915070c520c3d6a3e67052b4a2b1b7b127 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 21 Sep 2021 10:27:31 -0400
Subject: tried to make captions more readable when zoomed out by playing with
 scaling and overflow.  removed sidebar button from captions.

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

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

diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 7f164ca48..1ec7bf72a 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -175,6 +175,7 @@
             position: absolute;
             bottom: 0;
             width: 100%;
+            overflow-y: scroll;
             transform-origin: bottom left;
             opacity: 0.1;
             transition: opacity 0.5s;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 60ceac007..3e15ed661 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -972,19 +972,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
     @computed get innards() {
         TraceMobx();
+        const ffscale = (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
         const showTitle = this.ShowTitle?.split(":")[0];
         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"
-                style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, }}>
+                style={{
+                    pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+                    minWidth: 50 * ffscale,
+                    maxHeight: `max(100%, ${20 * ffscale}px)`
+                }}>
                 <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={showCaption}
-                    fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)}
+                    fontSize={12 * Math.max(1, 2 * ffscale / 3)}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
+                    noSidebar={true}
+                    dontScale={true}
                     isContentActive={this.isContentActive}
                     onClick={this.onClickFunc}
                 />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ebd509669..617fa0bee 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -833,7 +833,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.componentHeights = reaction(  // set the document height when one of the component heights changes and autoHeight is on
             () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
             ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
-                autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight));
+                autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
             }, { fireImmediately: true });
         this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
             newLinks => {
-- 
cgit v1.2.3-70-g09d2


From eb50e46332c2f0b48c7cb165d55245aab317ef08 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 21 Sep 2021 19:43:32 -0400
Subject: fixed up some darkScheme css, enabled comic mode for developers,
 enabled opening up fields on dashboards in myDocuments

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 30 ++++++++++------
 src/client/util/SelectionManager.ts                | 14 ++------
 src/client/util/SettingsManager.tsx                | 19 +++++-----
 src/client/views/ContextMenu.scss                  |  1 -
 src/client/views/DocumentDecorations.scss          | 28 +++++++++++----
 src/client/views/DocumentDecorations.tsx           | 42 +++++++++++++---------
 src/client/views/MainView.scss                     | 12 +++----
 src/client/views/MainView.tsx                      | 14 ++++----
 src/client/views/StyleProvider.tsx                 |  5 +--
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 src/client/views/collections/TreeView.tsx          |  9 +++--
 .../CollectionFreeFormLayoutEngines.tsx            |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 ++-
 .../collectionSchema/CollectionSchemaView.tsx      |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      |  2 --
 .../views/nodes/formattedText/DashDocView.tsx      |  3 +-
 src/mobile/MobileInterface.tsx                     |  4 +--
 19 files changed, 118 insertions(+), 89 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fafbc4a7d..206f65bfd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -862,7 +862,7 @@ export namespace Docs {
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
             const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
             const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d5dc9e2be..568a9ddbd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -20,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 { TreeView } from "../views/collections/TreeView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -38,7 +39,6 @@ import { ColorScheme } from "./SettingsManager";
 import { SharingManager } from "./SharingManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
-import { TreeView } from "../views/collections/TreeView";
 
 interface Button {
     title?: string;
@@ -816,16 +816,19 @@ export class CurrentUserUtils {
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
                 explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
             }));
-            // const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
-            // const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
-            // const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
+            const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
+            const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+            const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
             const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
             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", "user-friends", "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"]);
+            const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()');
+            // (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", "user-friends", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
+            (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "chalkboard", "tv", "camera", "users", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuFilters = new List<ScriptField>([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]);
         }
         return doc.myDashboards as any as Doc;
     }
@@ -1302,7 +1305,6 @@ export class CurrentUserUtils {
             }, { fireImmediately: true });
         // Document properties on load
         doc.system = true;
-        doc.darkScheme = ColorScheme.Dark;
         doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
         doc.title = Doc.CurrentUserEmail;
         doc._raiseWhenDragged = true;
@@ -1321,7 +1323,6 @@ export class CurrentUserUtils {
         doc.fontColor = StrCast(doc.fontColor, "black");
         doc.fontHighlight = StrCast(doc.fontHighlight, "");
         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);
         doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
@@ -1358,6 +1359,10 @@ export class CurrentUserUtils {
         // });
         setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
         doc.fieldInfos = await Docs.setupFieldInfos();
+        if (doc.activeDashboard instanceof Doc) {
+            // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+            doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
+        }
         return doc;
     }
 
@@ -1637,4 +1642,7 @@ Scripting.addGlobal(function makeTopLevelFolder() {
     const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
     TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
     return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
+});
+Scripting.addGlobal(function toggleComicMode() {
+    Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
 });
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index bac13373c..e507ec3bf 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,7 +2,6 @@ import { action, observable, ObservableMap } from "mobx";
 import { computedFn } from "mobx-utils";
 import { Doc, Opt } from "../../fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { DocumentView } from "../views/nodes/DocumentView";
 
@@ -13,12 +12,10 @@ export namespace SelectionManager {
         @observable IsDragging: boolean = false;
         SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
         @observable SelectedSchemaDocument: Doc | undefined;
-        @observable SelectedSchemaCollection: CollectionSchemaView | undefined;
 
         @action
-        SelectSchemaView(collectionView: Opt<CollectionSchemaView>, doc: Opt<Doc>) {
+        SelectSchemaViewDoc(doc: Opt<Doc>) {
             manager.SelectedSchemaDocument = doc;
-            manager.SelectedSchemaCollection = collectionView;
         }
         @action
         SelectView(docView: DocumentView, ctrlPressed: boolean): void {
@@ -33,7 +30,6 @@ export namespace SelectionManager {
             } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) {
                 Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
-                manager.SelectedSchemaCollection = undefined;
                 manager.SelectedViews.clear();
                 manager.SelectedViews.set(docView, docView.rootDoc);
             }
@@ -47,7 +43,6 @@ export namespace SelectionManager {
         }
         @action
         DeselectAll(): void {
-            manager.SelectedSchemaCollection = undefined;
             manager.SelectedSchemaDocument = undefined;
             Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenChildContentsActiveChanged(false));
             manager.SelectedViews.clear();
@@ -62,8 +57,8 @@ export namespace SelectionManager {
     export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
         manager.SelectView(docView, ctrlPressed);
     }
-    export function SelectSchemaView(colSchema: Opt<CollectionSchemaView>, document: Opt<Doc>): void {
-        manager.SelectSchemaView(colSchema, document);
+    export function SelectSchemaViewDoc(document: Opt<Doc>): void {
+        manager.SelectSchemaViewDoc(document);
     }
 
     const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {  // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
@@ -96,9 +91,6 @@ export namespace SelectionManager {
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
-    export function SelectedSchemaCollection(): CollectionSchemaView | undefined {
-        return manager.SelectedSchemaCollection;
-    }
     export function Docs(): Doc[] {
         return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index bd91db779..6a26dfdc7 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -19,9 +19,9 @@ export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
 export enum ColorScheme {
-    Dark = "Dark",
-    Light = "Light",
-    System = "Match System"
+    Dark = "-Dark",
+    Light = "-Light",
+    System = "-MatchSystem"
 }
 
 @observer
@@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = "Accounts";
 
     @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
-    @computed get colorScheme() { return Doc.UserDoc().colorScheme; }
+    @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; }
 
     constructor(props: {}) {
         super(props);
@@ -81,16 +81,16 @@ export class SettingsManager extends React.Component<{}> {
         const scheme: ColorScheme = (e.currentTarget as any).value;
         switch (scheme) {
             case ColorScheme.Light:
-                Doc.UserDoc().colorScheme = ColorScheme.Light;
+                CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
                 break;
             case ColorScheme.Dark:
-                Doc.UserDoc().colorScheme = ColorScheme.Dark;
+                CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark;
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
                 break;
             case ColorScheme.System: default:
                 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
-                    Doc.UserDoc().colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light;
+                    CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 });
                 break;
         }
@@ -119,6 +119,7 @@ export class SettingsManager extends React.Component<{}> {
         </div>;
 
         const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
+        const schemeMap = ["Light", "Dark", "Match system"];
 
         return <div className="colors-content">
             <div className="preferences-color">
@@ -132,8 +133,8 @@ export class SettingsManager extends React.Component<{}> {
             <div className="preferences-colorScheme">
                 <div className="preferences-color-text">Color Scheme</div>
                 <div className="preferences-color-controls">
-                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.UserDoc().colorScheme)}>
-                        {colorSchemes.map(scheme => <option key={scheme} value={scheme}> {scheme} </option>)}
+                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}>
+                        {colorSchemes.map((scheme, i) => <option key={scheme} value={scheme}> {schemeMap[i]} </option>)}
                     </select>
                 </div>
             </div>
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 47ae0424b..ea24dbf6d 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -8,7 +8,6 @@
     flex-direction: column;
     background: whitesmoke;
     border-radius: 3px;
-    border: solid $light-gray 1px;
 }
 
 // .contextMenu-item:first-child {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a9f50f81b..d8ad47ecb 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -2,10 +2,14 @@
 
 $linkGap: 3px;
 
+.documentDecorations-Dark,
 .documentDecorations {
   position: absolute;
   z-index: 2000;
 }
+.documentDecorations-Dark {
+    background: dimgray;
+}
 .documentDecorations-container {
     z-index: $docDecorations-zindex;
     position: absolute;
@@ -50,12 +54,17 @@ $linkGap: 3px;
         pointer-events: auto;
         background: $medium-gray;
         opacity: 0.1;
-
         &:hover {
             opacity: 1;
         }
     }
 
+    .documentDecorations-resizer-Dark
+    {
+        background: $light-gray;
+        opacity: 0.2;
+    }
+
     .documentDecorations-topLeftResizer,
     .documentDecorations-leftResizer,
     .documentDecorations-bottomLeftResizer {
@@ -221,6 +230,7 @@ $linkGap: 3px;
     cursor: ns-resize;
   }
 
+    .documentDecorations-title-Dark,
     .documentDecorations-title {
         opacity: 1;
         grid-column-start: 2;
@@ -233,14 +243,22 @@ $linkGap: 3px;
         height: 22px;
         position: absolute;
 
-        .documentDecorations-titleSpan {
+        .documentDecorations-titleSpan, 
+        .documentDecorations-titleSpan-Dark  {
             width: 100%;
             border-radius: 8px;
-            background: #ffffffcf;
+            background: #ffffffa0;
             position: absolute;
             display: inline-block;
             cursor: move;
         }
+        .documentDecorations-titleSpan-Dark {
+            background: hsla(0, 0%, 0%, 0.412);
+        }
+    }
+    .documentDecorations-title-Dark {
+        color: white;
+        background: black;
     }
 
   .documentDecorations-contextMenu {
@@ -439,10 +457,6 @@ $linkGap: 3px;
   }
 }
 
-.documentDecorations-darkScheme {
-  background: dimgray;
-}
-
 #template-list {
   position: absolute;
   top: 25px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d785d5419..e9a54d6a5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSy
 import { Document } from '../../fields/documentSchemas';
 import { InkField } from "../../fields/InkField";
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { Docs } from "../documents/Documents";
@@ -27,6 +27,8 @@ import { LightboxView } from './LightboxView';
 import { DocumentView } from "./nodes/DocumentView";
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import React = require("react");
+import { dark } from '@material-ui/core/styles/createPalette';
+import { color } from 'd3-color';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -241,7 +243,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
             .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
 
-        if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+        const resizeHdl = this._resizeHdlId.split(" ")[0];
+        if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
             const project = (p: number[], a: number[], b: number[]) => {
                 const atob = [b[0] - a[0], b[1] - a[1]];
                 const atop = [p[0] - a[0], p[1] - a[1]];
@@ -264,7 +267,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._snapY = thisPt.y;
         let dragBottom = false, dragRight = false, dragBotRight = false;
         let dX = 0, dY = 0, dW = 0, dH = 0;
-        switch (this._resizeHdlId) {
+        switch (this._resizeHdlId.split(" ")[0]) {
             case "": break;
             case "documentDecorations-topLeftResizer":
                 dX = -1;
@@ -437,11 +440,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 </div>
             </Tooltip>);
 
+        const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
         const titleArea = this._edtingTitle ?
-            <input ref={this._keyinput} className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
-                onBlur={e => this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> :
+            <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
+                style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }}
+                type="text" name="dynbox" autoComplete="on"
+                value={this._accumulatedTitle}
+                onBlur={e => this.titleBlur()}
+                onChange={action(e => this._accumulatedTitle = e.target.value)}
+                onKeyPress={this.titleEntered} /> :
             <div className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} >
-                <span className="documentDecorations-titleSpan">{`${this.selectionTitle}`}</span>
+                <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
             </div>;
 
         let inMainMenuPanel = false;
@@ -457,8 +466,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldoc.rootDoc.type === DocumentType.INK;
+        const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
 
-        return (<div className="documentDecorations" style={{ background: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : "" }} >
+        return (<div className={`documentDecorations${colorScheme}`}>
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
@@ -481,15 +491,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         <>
                             {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
                                 topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
-                            <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="l" className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="c" className="documentDecorations-centerCont"></div>
-                            <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+                            <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
 
                             {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
                                 topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4f871f5ec..7fa841002 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -41,7 +41,7 @@
 }
 
 .mainView-container,
-.mainView-container-dark {
+.mainView-container-Dark {
     width: 100%;
     height: 100%;
     position: absolute;
@@ -65,7 +65,7 @@
     }
 }
 
-.mainView-container-dark {
+.mainView-container-Dark {
     color: $light-gray;
 
     .lm_goldenlayout {
@@ -91,7 +91,7 @@
 
     .contextMenu-cont,
     .contextMenu-item {
-        background: $medium-gray;
+        background: $dark-gray;
     }
 
     .contextMenu-item:hover {
@@ -144,7 +144,7 @@
     }
 }
 
-.mainView-innerContent, .mainView-innerContent-dark {
+.mainView-innerContent, .mainView-innerContent-Dark {
     display: contents;
     flex-direction: row;
     position: relative;
@@ -175,7 +175,7 @@
 .mainView-libraryHandle {
     background-color: $light-gray;
 }
-.mainView-innerContent-dark 
+.mainView-innerContent-Dark 
 {
     .propertiesView {
         background-color: #252525;
@@ -198,7 +198,7 @@
         background: #353535;
     }
 }
-.mainView-container-dark {
+.mainView-container-Dark {
     .contextMenu-cont {
         background: $medium-gray;
         color: $white;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d854f118f..c99ba447c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
 import { Scripting } from '../util/Scripting';
 import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -88,7 +88,7 @@ export class MainView extends React.Component {
     @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
     @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
     @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+    @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); }
     @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
 
@@ -430,10 +430,10 @@ export class MainView extends React.Component {
         const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
         return <>
             {this.leftMenuPanel}
-            <div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
+            <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                 {this.flyout}
                 <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
-                    <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
+                    <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
                 <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
                     <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
@@ -441,7 +441,7 @@ export class MainView extends React.Component {
                     {this.dockingContent}
 
                     <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
-                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? Colors.WHITE : Colors.BLACK} size="sm" />
+                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
                     </div>
                     <div className="properties-container">
                         {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
@@ -459,7 +459,7 @@ export class MainView extends React.Component {
                     this._dashUIHeight = r.getBoundingClientRect().height;
                 })).observe(r);
             }} style={{
-                color: this.darkScheme ? "rgb(205,205,205)" : "black",
+                color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
                 height: `calc(100% - ${this.topOfDashUI}px)`,
                 width: "100%",
             }} >
@@ -601,7 +601,7 @@ export class MainView extends React.Component {
     }
 
     render() {
-        return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}
+        return (<div className={`mainView-container${this.colorScheme}`}
             onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
             ref={r => {
                 r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3c88a4830..1eb7a222e 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -22,6 +22,7 @@ import "./StyleProvider.scss";
 import React = require("react");
 import Color = require('color');
 import { lightOrDark } from '../../Utils';
+import { ColorScheme } from '../util/SettingsManager';
 
 export enum StyleLayers {
     Background = "background"
@@ -49,7 +50,7 @@ export enum StyleProp {
     FontSize = "fontSize",                // size of text font
 }
 
-function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
 
 function toggleBackground(doc: Doc) {
     UndoManager.RunInBatch(() => runInAction(() => {
@@ -143,7 +144,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                     doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
                                         StrCast((props?.renderDepth || 0) > 0 ?
                                             Doc.UserDoc().activeCollectionNestedBackground :
-                                            Doc.UserDoc().activeCollectionBackground)));
+                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))));
                     break;
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6a22acae8..3ea190a98 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -353,7 +353,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 97de097e0..a3da0e0e4 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -481,13 +481,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get validExpandViewTypes() {
-        if (this.doc.viewType === CollectionViewType.Docking) return [this.fieldKey];
+        if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
+            return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
+        }
         const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
         const links = () => DocListCast(this.doc.links).length ? "links" : "";
-        const data = () => this.childDocs && !this.props.treeView.dashboardMode ? this.fieldKey : "";
+        const data = () => this.childDocs ? this.fieldKey : "";
         const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
         const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
-        return [data(), "layout", ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
+        const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+        return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
     expandNextviewType = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 37444a9dc..b3c57d33a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -7,6 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../../fields/Types";
 import { aggregateBounds } from "../../../../Utils";
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import React = require("react");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export interface ViewDefBounds {
     type: string;
@@ -361,7 +362,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 94cf1c5a6..0b12f6c21 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import Color = require("color");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -1419,6 +1420,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const renderGridSpace = gridSpace * this.zoomScaling();
         const w = this.props.PanelWidth() + 2 * renderGridSpace;
         const h = this.props.PanelHeight() + 2 * renderGridSpace;
+        const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
         return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
             ref={(el) => {
                 const ctx = el?.getContext('2d');
@@ -1429,7 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
                     ctx.clearRect(0, 0, w, h);
                     if (ctx) {
-                        ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
+                        ctx.strokeStyle = strokeStyle;
                         ctx.beginPath();
                         for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
                             ctx.moveTo(x, Cy - h);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index dfe99ffc8..12493ecc1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -337,8 +337,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {this.renderTypes(this._col)}
             {this.renderColors(this._col)}
             <div className="collectionSchema-headerMenu-group">
-                <button onClick={() => { this.deleteColumn(this._col.heading); }}
-                >Hide Column</button>
+                <button onClick={() => { this.deleteColumn(this._col.heading); }}>
+                    Hide Column
+                </button>
             </div>
         </div>;
     }
@@ -353,7 +354,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e15ed661..5d0b91b91 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -25,7 +25,6 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
-import { ColorScheme } from "../../util/SettingsManager";
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
@@ -50,6 +49,7 @@ import { ScriptingBox } from "./ScriptingBox";
 import { PresBox } from './trails/PresBox';
 import React = require("react");
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { ColorScheme } from "../../util/SettingsManager";
 const { Howl } = require('howler');
 
 interface Window {
@@ -1061,9 +1061,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     render() {
         TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
-            ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
-            ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
+        const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
         const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
         const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
         let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index adcac0aef..520034c3c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -14,7 +14,6 @@ import { WebField } from '../../../../fields/URLField';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Scripting } from "../../../util/Scripting";
 import { SelectionManager } from '../../../util/SelectionManager';
-import { ColorScheme } from '../../../util/SettingsManager';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { CollectionViewType } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
@@ -463,7 +462,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
     render() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark;
 
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index e519de1c5..149836e93 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -12,6 +12,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
 import React = require("react");
 import * as ReactDOM from 'react-dom';
 import { observer } from "mobx-react";
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export class DashDocView {
     _fieldWrapper: HTMLSpanElement; // container for label and value
@@ -20,7 +21,7 @@ export class DashDocView {
         this._fieldWrapper = document.createElement("span");
         this._fieldWrapper.style.position = "relative";
         this._fieldWrapper.style.textIndent = "0";
-        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
+        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray"));
         this._fieldWrapper.style.width = node.attrs.width;
         this._fieldWrapper.style.height = node.attrs.height;
         this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 404e828ea..652804126 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -17,7 +17,7 @@ import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
 import { DocumentType } from "../client/documents/DocumentTypes";
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { Scripting } from '../client/util/Scripting';
-import { SettingsManager } from '../client/util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from "../client/util/UndoManager";
 import { TabDocView } from '../client/views/collections/TabDocView';
@@ -403,7 +403,7 @@ export class MobileInterface extends React.Component {
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
 
-        const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+        const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`);
         const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
         const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
         dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
-- 
cgit v1.2.3-70-g09d2

