From 2b8e17103583fa70018477368f83b9d564961067 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 18 Aug 2020 16:02:10 -0400
Subject: changed catalog to pop up search over db for author.  turned off
 double-clicking on any fonticon.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d4c9f74d5..669289904 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -509,7 +509,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (node.isTextblock) {
             let index = 0, foundAt;
             const ep = this.getNodeEndpoints(pm.state.doc, node);
-            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
+            const regexp = find.replace("*", "");
+            if (regexp) while (ep && (foundAt = node.textContent.slice(index).search(regexp, "i")) > -1) {
                 const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
                 ret.push(sel);
                 index = index + foundAt + find.length;
-- 
cgit v1.2.3-70-g09d2


From f02d2ca93534141570a5dc01fda8165d1e6d22d5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 18 Aug 2020 16:15:42 -0400
Subject: fixed undoing typed text boxes to happen in one step (not undo to
 first character, then undo again fo whole box)

---
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 3 ++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx       | 3 +++
 2 files changed, 5 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c0b19fcd2..b6a0504e2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -12,7 +12,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
 import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { ContextMenu } from "../../ContextMenu";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { PreviewCursor } from "../../PreviewCursor";
@@ -138,6 +138,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                     tbox.layoutKey = "layout_" + StrCast(template.title);
                     Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
                 }
+                FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
                 this.props.addLiveTextDocument(tbox);
                 e.stopPropagation();
             }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 669289904..0f2f90c1d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1434,6 +1434,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
         return wasUndoing;
     }
+    public static LiveTextUndo: UndoManager.Batch | undefined;
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
@@ -1441,6 +1442,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.endUndoTypingBatch();
         this.doLinkOnDeselect();
 
+        FormattedTextBox.LiveTextUndo?.end();
+        FormattedTextBox.LiveTextUndo = undefined;
         // move the richtextmenu offscreen
         //if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
     }
-- 
cgit v1.2.3-70-g09d2


From 5b7d51d47a967e679d17560771efe71522ada13a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 19 Aug 2020 22:30:57 -0400
Subject: more cleanup of SearchBox.  made searchMatch be a tri-state for
 forward/backward/nomatch

---
 .../views/collections/CollectionSchemaCells.tsx    | 195 ++++++---------------
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  11 +-
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 4 files changed, 58 insertions(+), 152 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index f95de5201..626ddf288 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -35,6 +35,7 @@ import { DateField } from "../../../fields/DateField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SearchUtil } from "../../util/SearchUtil";
+import { DocumentType } from "../../documents/DocumentTypes";
 const path = require('path');
 
 library.add(faExpand);
@@ -146,10 +147,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
     }
 
     protected dropRef = (ele: HTMLElement | null) => {
-        this._dropDisposer && this._dropDisposer();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
-        }
+        this._dropDisposer?.();
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
     }
 
     // expandDoc = (e: React.PointerEvent) => {
@@ -326,7 +325,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                 ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
                 <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}>
                     <div className="collectionSchemaView-cellContents"
-                        ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}>
+                        ref={type === undefined || type === "document" ? this.dropRef : null}>
                         {!search ?
                             <EditableView
                                 positions={positions.length > 0 ? positions : undefined}
@@ -439,30 +438,22 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         );
     }
 
-    render() {
-        return this.renderCellWithType(undefined);
-    }
+    render() { return this.renderCellWithType(undefined); }
 }
 
 @observer
 export class CollectionSchemaNumberCell extends CollectionSchemaCell {
-    render() {
-        return this.renderCellWithType("number");
-    }
+    render() { return this.renderCellWithType("number"); }
 }
 
 @observer
 export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
-    render() {
-        return this.renderCellWithType("boolean");
-    }
+    render() { return this.renderCellWithType("boolean"); }
 }
 
 @observer
 export class CollectionSchemaStringCell extends CollectionSchemaCell {
-    render() {
-        return this.renderCellWithType("string");
-    }
+    render() { return this.renderCellWithType("string"); }
 }
 
 @observer
@@ -792,25 +783,14 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
         this._opened = open;
     }
 
-    // @action
-    // onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    //     this._text = e.target.value;
-
-    //     // change if its a document
-    //     this._optionsList[this._selectedNum] = this._text;
-    // }
-
     @action
     onSetValue = (value: string) => {
-
-
         this._text = value;
 
         // change if its a document
         this._optionsList[this._selectedNum] = this._text;
 
         (this.prop.Document[this.prop.fieldKey] as List<any>).splice(this._selectedNum, 1, value);
-
     }
 
     @action
@@ -824,55 +804,34 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
         this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
     }
 
-
     render() {
-
-        const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
         let type = "list";
-
         let link = false;
-        let doc = false;
         const reference = React.createRef<HTMLDivElement>();
 
         if (typeof this._field === "object" && this._optionsList[0]) {
-
-            const options = this._optionsList.map((element, index) => {
-
-                if (element instanceof Doc) {
-                    doc = true;
-                    type = "document";
-                    if (this.prop.fieldKey.toLowerCase() === "links") {
-                        link = true;
-                        type = "link";
+            const options = !this._opened ? (null) : <div>
+                {this._optionsList.map((element, index) => {
+                    let title = "";
+                    if (element instanceof Doc) {
+                        type = "document";
+                        if (this.prop.fieldKey.toLowerCase() === "links") {
+                            link = true;
+                            type = "link";
+                        }
+                        title = StrCast(element.title);
                     }
-                    const document = FieldValue(Cast(element, Doc));
-                    const title = element.title;
-                    return <div
-                        className="collectionSchemaView-dropdownOption"
-                        onPointerDown={(e) => { this.onSelected(StrCast(element.title), index); }}
-                        style={{ padding: "6px" }}>
+                    return <div className="collectionSchemaView-dropdownOption" style={{ padding: "6px" }}
+                        onPointerDown={(e) => this.onSelected(StrCast(element), index)} >
+                        {element}
                         {title}
                     </div>;
-
-                } else {
-                    return <div
-                        className="collectionSchemaView-dropdownOption"
-                        onPointerDown={(e) => { this.onSelected(StrCast(element), index); }}
-                        style={{ padding: "6px" }}>{element}</div>;
-                }
-            });
+                })}
+            </div>;
 
             const plainText = <div style={{ padding: "5.9px" }}>{this._text}</div>;
-            // const textarea = <textarea onChange={this.onChange} value={this._text}
-            //     onFocus={doc ? this.onFocus : undefined}
-            //     onBlur={doc ? e => this._overlayDisposer?.() : undefined}
-            //     style={{ resize: "none" }}
-            //     placeholder={"select an item"}></textarea>;
-
-            const textarea = <div className="collectionSchemaView-cellContents"
-                style={{ padding: "5.9px" }}
-                ref={type === undefined || type === "document" ? this.dropRef : null} key={this.prop.Document[Id]}>
+            const textarea = <div className="collectionSchemaView-cellContents" key={this.prop.Document[Id]} style={{ padding: "5.9px" }}
+                ref={type === undefined || type === "document" ? this.dropRef : null} >
                 <EditableView
                     editing={this._isEditing}
                     isEditingCallback={this.isEditingCallback}
@@ -880,11 +839,8 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
                     contents={this._text}
                     height={"auto"}
                     maxHeight={Number(MAX_ROW_HEIGHT)}
-                    GetValue={() => {
-                        return this._text;
-                    }}
+                    GetValue={() => this._text}
                     SetValue={action((value: string) => {
-
                         // add special for params 
                         this.onSetValue(value);
                         return true;
@@ -893,37 +849,26 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
             </div >;
 
             //☰
-
-            const dropdown = <div>
-                {options}
-            </div>;
-
             return (
                 <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
                     <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
                         <div className="collectionSchemaView-dropDownWrapper">
-                            <button type="button" className="collectionSchemaView-dropdownButton" onClick={(e) => { this.toggleOpened(!this._opened); }}
-                                style={{ right: "length", position: "relative" }}>
-                                {this._opened ? <FontAwesomeIcon icon="caret-up" size="lg" ></FontAwesomeIcon>
-                                    : <FontAwesomeIcon icon="caret-down" size="lg" ></FontAwesomeIcon>}
+                            <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: "length", position: "relative" }}
+                                onClick={e => this.toggleOpened(!this._opened)} >
+                                <FontAwesomeIcon icon={this._opened ? "caret-up" : "caret-down"} size="lg" />
                             </button>
                             <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
                         </div>
-
-                        {this._opened ? dropdown : null}
+                        {options}
                     </div >
                 </div>
             );
-        } else {
-            return this.renderCellWithType("list");
         }
+        return this.renderCellWithType("list");
     }
 }
 
 
-
-
-
 @observer
 export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
     @observable private _isChecked: boolean = typeof this.props.rowProps.original[this.props.rowProps.column.id as string] === "boolean" ? BoolCast(this.props.rowProps.original[this.props.rowProps.column.id as string]) : false;
@@ -932,15 +877,13 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
     toggleChecked = (e: React.ChangeEvent<HTMLInputElement>) => {
         this._isChecked = e.target.checked;
         const script = CompileScript(e.target.checked.toString(), { requiredType: "boolean", addReturn: true, params: { this: Doc.name } });
-        if (script.compiled) {
-            this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
-        }
+        script.compiled && this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
     }
 
     render() {
         const reference = React.createRef<HTMLDivElement>();
         const onItemDown = (e: React.PointerEvent) => {
-            (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
+            (!this.props.CollectionView?.props.isSelected() ? undefined :
                 SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         };
         return (
@@ -956,62 +899,28 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
 
 @observer
 export class CollectionSchemaButtons extends CollectionSchemaCell {
-
     render() {
+        const doc = this.props.rowProps.original;
+        const searchMatch = (backward: boolean = true) => { doc.searchMatch = undefined; setTimeout(() => doc.searchMatch = backward, 0); };
         // const reference = React.createRef<HTMLDivElement>();
         // const onItemDown = (e: React.PointerEvent) => {
         //     (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
         //         SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         // };
-        const doc = this.props.rowProps.original;
-        let buttons: JSX.Element | undefined = undefined;
-        buttons = <div style={{
-            paddingTop: 8,
-            paddingLeft: 3,
-        }}><button onClick={() => {
-            doc.searchMatch = false;
-            setTimeout(() => doc.searchMatch = true, 0);
-        }} style={{ padding: 2, left: 77 }}>
-                <FontAwesomeIcon icon="arrow-up" size="sm" />
-            </button>
-            <button onClick={() => {
-                {
-                    doc.searchMatchAlt = false;
-                    setTimeout(() => doc.searchMatchAlt = true, 0);
-                }
-            }} style={{ padding: 2 }}>
-                <FontAwesomeIcon icon="arrow-down" size="sm" />
-            </button></div>;
-        const type = StrCast(doc.type);
-        if (type === "pdf") {
-            buttons = <div><button
-                style={{
-                    position: "relative",
-                    height: 30,
-                    width: 28,
-                    left: 1,
-                }}
-
-                onClick={() => {
-                    doc.searchMatch = false;
-                    setTimeout(() => doc.searchMatch = true, 0);
-                }}>
-                <FontAwesomeIcon icon="arrow-down" size="sm" />
-            </button></div >;
-        }
-        else if (type !== "rtf") {
-            buttons = undefined;
-        }
-
-        if (BoolCast(this.props.Document._searchDoc) === true) {
-
-        }
-        else {
-            buttons = undefined;
-        }
-        return (
-            <div> {buttons}</div>
-        );
-    }
-}
-
+        return !BoolCast(this.props.Document._searchDoc) ? <></>
+            : StrCast(doc.type) === DocumentType.PDF ?
+                <button style={{ position: "relative", height: 30, width: 28, left: 1, }} onClick={() => searchMatch(false)}>
+                    <FontAwesomeIcon icon="arrow-down" size="sm" />
+                </button>
+                : StrCast(doc.type) === DocumentType.RTF ?
+                    <div style={{ paddingTop: 8, paddingLeft: 3, }} >
+                        <button style={{ padding: 2, left: 77 }} onClick={() => searchMatch(true)}>
+                            <FontAwesomeIcon icon="arrow-up" size="sm" />
+                        </button>
+                        <button style={{ padding: 2 }} onClick={() => searchMatch(false)} >
+                            <FontAwesomeIcon icon="arrow-down" size="sm" />
+                        </button>
+                    </div> :
+                    <></>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f23fa8eb6..0f6274663 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -469,7 +469,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
                 style={{
-                    fontWeight: this.doc.searchMatch ? "bold" : undefined,
+                    fontWeight: this.doc.searchMatch !== undefined ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.doc.workspaceBrush) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0f2f90c1d..d58d75226 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -374,7 +374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
         }
     }
-    public highlightSearchTerms = (terms: string[], alt: boolean) => {
+    public highlightSearchTerms = (terms: string[], backward: boolean) => {
         if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
 
 
@@ -391,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
             else {
                 this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-                if (alt === true) {
+                if (backward === true) {
                     if (this._searchIndex > 1) {
                         this._searchIndex += -2;
                     }
@@ -907,12 +907,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         this.setupEditor(this.config, this.props.fieldKey);
 
-        this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt,
-            search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(),
-            { fireImmediately: true });
         this._disposers.search = reaction(() => this.rootDoc.searchMatch,
-            search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(),
-            { fireImmediately: this.rootDoc.searchMatch ? true : false });
+            search => search !== undefined ? this.highlightSearchTerms([Doc.SearchQuery()], BoolCast(search)) : this.unhighlightSearchTerms(),
+            { fireImmediately: this.rootDoc.searchMatch !== undefined ? true : false });
 
         this._disposers.record = reaction(() => this._recording,
             () => {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 965d568e6..0916e8b0c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -152,7 +152,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0);
         this._searchReactionDisposer = reaction(() => this.Document.searchMatch,
             m => {
-                if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), true);
+                if (m !== undefined) (this._lastSearch = true) && this.search(Doc.SearchQuery(), true);
                 else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200);
             }, { fireImmediately: true });
 
-- 
cgit v1.2.3-70-g09d2


From da8901bef2d4a0362354c69fe486076a67f8efc4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 19 Aug 2020 22:39:51 -0400
Subject: cleaning lint errors

---
 src/client/views/PropertiesButtons.tsx                    |  2 +-
 src/client/views/collections/CollectionMenu.tsx           | 12 ++++++------
 src/client/views/collections/CollectionSchemaCells.tsx    | 11 ++---------
 src/client/views/collections/CollectionSchemaHeaders.tsx  |  2 +-
 src/client/views/collections/SchemaTable.tsx              |  2 +-
 src/client/views/nodes/DocumentView.tsx                   |  2 +-
 src/client/views/nodes/PresBox.tsx                        |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++----
 src/fields/Doc.ts                                         | 10 +++++-----
 9 files changed, 24 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index e352db562..9e776c652 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -301,7 +301,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
             const [left, top] = [e.clientX, e.clientY];
             dragData.dropAction = "alias";
-            DragManager.StartDocumentDrag([this._dragRef.current!], dragData, left, top, {
+            DragManager.StartDocumentDrag([this._dragRef.current], dragData, left, top, {
                 offsetX: dragData.offset[0],
                 offsetY: dragData.offset[1],
                 hideSource: false
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cf053e1ca..388eda2b3 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -624,10 +624,10 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     }
 
     @computed get _url() {
-        return this.selectedDoc ? Cast(this.selectedDoc["data"], WebField, null)?.url.toString() : "hello";
+        return this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : "hello";
     }
 
-    set _url(value) { this.selectedDoc && (this.selectedDoc["data"] = value); }
+    set _url(value) { this.selectedDoc && (this.selectedDoc.data = value); }
 
     @action
     submitURL = () => {
@@ -637,7 +637,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
             const future = this.selectedDoc ? Cast(this.selectedDoc["data-future"], listSpec("string"), null) : null;
             const history = this.selectedDoc ? Cast(this.selectedDoc["data-history"], listSpec("string"), null) : [];
             const annos = this.selectedDoc ? DocListCast(this.selectedDoc["data-annotations"]) : undefined;
-            const url = this.selectedDoc ? Cast(this.selectedDoc["data"], WebField, null)?.url.toString() : null;
+            const url = this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : null;
             if (url) {
                 if (history === undefined) {
                     this.selectedDoc && (this.selectedDoc["data-history"] = new List<string>([url]));
@@ -648,7 +648,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
                 future && (future.length = 0);
                 this.selectedDoc && (this.selectedDoc["data-" + this.urlHash(url)] = new List<Doc>(annos));
             }
-            this.selectedDoc && (this.selectedDoc["data"] = new WebField(URLy));
+            this.selectedDoc && (this.selectedDoc.data = new WebField(URLy));
             this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>([]));
         } catch (e) {
             console.log("WebBox URL error:" + this._url);
@@ -682,7 +682,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
         if (future?.length) {
             history?.push(this._url);
             this.selectedDoc && (this.selectedDoc["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"])));
-            this.selectedDoc && (this.selectedDoc["data"] = new WebField(new URL(this._url = future.pop()!)));
+            this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = future.pop()!)));
             this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)])));
         }
     }
@@ -695,7 +695,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
             if (future === undefined) this.selectedDoc && (this.selectedDoc["data-future"] = new List<string>([this._url]));
             else future.push(this._url);
             this.selectedDoc && (this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"])));
-            this.selectedDoc && (this.selectedDoc["data"] = new WebField(new URL(this._url = history.pop()!)));
+            this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = history.pop()!)));
             this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)])));
         }
     }
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 626ddf288..ea786800c 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -778,17 +778,10 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
     @observable private _text = "select an item";
     @observable private _selectedNum = 0;
 
-    @action
-    toggleOpened(open: boolean) {
-        this._opened = open;
-    }
-
     @action
     onSetValue = (value: string) => {
-        this._text = value;
-
         // change if its a document
-        this._optionsList[this._selectedNum] = this._text;
+        this._optionsList[this._selectedNum] = this._text = value;
 
         (this.prop.Document[this.prop.fieldKey] as List<any>).splice(this._selectedNum, 1, value);
     }
@@ -854,7 +847,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
                     <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
                         <div className="collectionSchemaView-dropDownWrapper">
                             <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: "length", position: "relative" }}
-                                onClick={e => this.toggleOpened(!this._opened)} >
+                                onClick={action(e => this._opened = !this._opened)} >
                                 <FontAwesomeIcon icon={this._opened ? "caret-up" : "caret-down"} size="lg" />
                             </button>
                             <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 73f319e9b..34a8bfa24 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -275,7 +275,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
         document.removeEventListener("pointerdown", this.detectClick);
         const filters = Cast(this.props.Document._docFilters, listSpec("string"));
         if (filters?.includes(this._key)) {
-            runInAction(() => { this.closeResultsVisibility = "contents" });
+            runInAction(() => this.closeResultsVisibility = "contents");
         }
     }
 
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 489c5e540..6ec9783e2 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -415,7 +415,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     createRow = action(() => {
         this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
         this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
-    })
+    });
 
     @undoBatch
     @action
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7cf6b0f39..590befd86 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -713,7 +713,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
-        if (!cm || ((e as any)?.nativeEvent as any)?.SchemaHandled) return;
+        if (!cm || (e?.nativeEvent as any)?.SchemaHandled) return;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f39413e9f..d30ea03b1 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1061,7 +1061,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 DockedFrameRenderer.PinDoc(doc, false);
                 this.gotoDocument(this.childDocs.length, this.itemIndex);
             } else {
-                this.props.addDocTab(doc as Doc, "onRight");
+                this.props.addDocTab(doc, "onRight");
             }
         }
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d58d75226..6b4115e53 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -510,10 +510,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let index = 0, foundAt;
             const ep = this.getNodeEndpoints(pm.state.doc, node);
             const regexp = find.replace("*", "");
-            if (regexp) while (ep && (foundAt = node.textContent.slice(index).search(regexp, "i")) > -1) {
-                const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
-                ret.push(sel);
-                index = index + foundAt + find.length;
+            if (regexp) {
+                while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
+                    const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
+                    ret.push(sel);
+                    index = index + foundAt + find.length;
+                }
             }
         } else {
             node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4797fb4cb..3fdeb8e71 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -206,11 +206,11 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [FieldsSym] = (clear?: boolean) => { return clear ? this.___fields = this.___fieldKeys = {} : this.___fields; }
-    public [WidthSym] = () => { return NumCast(this[SelfProxy]._width); }
-    public [HeightSym] = () => { return NumCast(this[SelfProxy]._height); }
-    public [ToScriptString] = () => { return `DOC-"${this[Self][Id]}"-`; }
-    public [ToString] = () => { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
+    public [FieldsSym] = (clear?: boolean) => clear ? this.___fields = this.___fieldKeys = {} : this.___fields;
+    public [WidthSym] = () => NumCast(this[SelfProxy]._width);
+    public [HeightSym] = () => NumCast(this[SelfProxy]._height);
+    public [ToScriptString] = () => `DOC-"${this[Self][Id]}"-`;
+    public [ToString] = () => `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`;
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     public get [DataSym]() {
         const self = this[SelfProxy];
-- 
cgit v1.2.3-70-g09d2


From 4ed30f48bd0c87f0a5ccb484d78bf4601ab6ff74 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 22 Aug 2020 09:49:56 -0400
Subject: switched search from writiing searchMatch to matched docs to instead
 store in a run-time observableMap.  This allows simultaneous searches by
 different users.

---
 .../views/collections/CollectionSchemaCells.tsx    | 26 ++++++---------
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/nodes/FieldView.tsx               |  3 --
 .../views/nodes/formattedText/FormattedTextBox.tsx | 39 ++++++++++------------
 src/client/views/pdf/PDFViewer.tsx                 |  4 +--
 src/client/views/search/SearchBox.tsx              | 34 ++++++++-----------
 src/fields/Doc.ts                                  | 25 ++++++++++++++
 7 files changed, 69 insertions(+), 64 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index ea786800c..4bd69041b 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -894,26 +894,22 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
 export class CollectionSchemaButtons extends CollectionSchemaCell {
     render() {
         const doc = this.props.rowProps.original;
-        const searchMatch = (backward: boolean = true) => { doc.searchMatch = undefined; setTimeout(() => doc.searchMatch = backward, 0); };
+        const searchMatch = (backward: boolean = true) => Doc.SearchMatchNext(doc, backward);
         // const reference = React.createRef<HTMLDivElement>();
         // const onItemDown = (e: React.PointerEvent) => {
         //     (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
         //         SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         // };
         return !BoolCast(this.props.Document._searchDoc) ? <></>
-            : StrCast(doc.type) === DocumentType.PDF ?
-                <button style={{ position: "relative", height: 30, width: 28, left: 1, }} onClick={() => searchMatch(false)}>
-                    <FontAwesomeIcon icon="arrow-down" size="sm" />
-                </button>
-                : StrCast(doc.type) === DocumentType.RTF ?
-                    <div style={{ paddingTop: 8, paddingLeft: 3, }} >
-                        <button style={{ padding: 2, left: 77 }} onClick={() => searchMatch(true)}>
-                            <FontAwesomeIcon icon="arrow-up" size="sm" />
-                        </button>
-                        <button style={{ padding: 2 }} onClick={() => searchMatch(false)} >
-                            <FontAwesomeIcon icon="arrow-down" size="sm" />
-                        </button>
-                    </div> :
-                    <></>;
+            : [DocumentType.PDF, DocumentType.RTF].includes(StrCast(doc.type) as DocumentType) ?
+                <div style={{ paddingTop: 8, paddingLeft: 3, }} >
+                    <button style={{ padding: 2, left: 77 }} onClick={() => searchMatch(true)}>
+                        <FontAwesomeIcon icon="arrow-up" size="sm" />
+                    </button>
+                    <button style={{ padding: 2 }} onClick={() => searchMatch(false)} >
+                        <FontAwesomeIcon icon="arrow-down" size="sm" />
+                    </button>
+                </div> :
+                <></>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4503b0fb3..8440d3e9b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -472,7 +472,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
                 style={{
-                    fontWeight: this.doc.searchMatch !== undefined ? "bold" : undefined,
+                    fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.doc.dashboardBrush) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d95951f3a..3726f1d5f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -58,9 +58,6 @@ export interface FieldViewProps {
 
     ChromeHeight?: () => number;
     childLayoutTemplate?: () => Opt<Doc>;
-    highlighting?: string[];
-    lines?: string[];
-    doc?: Doc;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     height?: number;
     width?: number;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6b4115e53..5250a8f58 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -376,8 +376,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     public highlightSearchTerms = (terms: string[], backward: boolean) => {
         if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-
-
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
             const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
@@ -385,24 +383,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let tr = this._editorView.state.tr;
             const flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
-            if (BoolCast(Doc.GetProto(this.dataDoc).resetSearch) === true) {
-                this._searchIndex = 0;
-                Doc.GetProto(this.dataDoc).resetSearch = undefined;
-            }
-            else {
-                this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-                if (backward === true) {
-                    if (this._searchIndex > 1) {
-                        this._searchIndex += -2;
-                    }
-                    else if (this._searchIndex === 1) {
-                        this._searchIndex = length - 1;
-                    }
-                    else if (this._searchIndex === 0 && length !== 1) {
-                        this._searchIndex = length - 2;
-                    }
-
+            console.log("Search:" + this.rootDoc.title + " " + this._searchIndex + " => " + (this._searchIndex + 1 > flattened.length - 1 ? 0 : this._searchIndex + 1));
+            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            if (backward === true) {
+                if (this._searchIndex > 1) {
+                    this._searchIndex += -2;
+                }
+                else if (this._searchIndex === 1) {
+                    this._searchIndex = length - 1;
                 }
+                else if (this._searchIndex === 0 && length !== 1) {
+                    this._searchIndex = length - 2;
+                }
+
             }
 
             const lastSel = Math.min(flattened.length - 1, this._searchIndex);
@@ -909,9 +902,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         this.setupEditor(this.config, this.props.fieldKey);
 
-        this._disposers.search = reaction(() => this.rootDoc.searchMatch,
-            search => search !== undefined ? this.highlightSearchTerms([Doc.SearchQuery()], BoolCast(search)) : this.unhighlightSearchTerms(),
-            { fireImmediately: this.rootDoc.searchMatch !== undefined ? true : false });
+        this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
+            search => {
+                search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms();
+            },
+            { fireImmediately: Doc.IsSearchMatch(this.rootDoc) ? true : false });
 
         this._disposers.record = reaction(() => this._recording,
             () => {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0916e8b0c..f2aa9fa3f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -150,9 +150,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
         this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0);
-        this._searchReactionDisposer = reaction(() => this.Document.searchMatch,
+        this._searchReactionDisposer = reaction(() => Doc.IsSearchMatch(this.rootDoc),
             m => {
-                if (m !== undefined) (this._lastSearch = true) && this.search(Doc.SearchQuery(), true);
+                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 });
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index d7c065a75..c571adefe 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -84,21 +84,17 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
-    @action
-    getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc)
-
     @action.bound
     onChange(e: React.ChangeEvent<HTMLInputElement>) {
         this.layoutDoc._searchString = e.target.value;
         this.newsearchstring = e.target.value;
         if (e.target.value === "") {
             if (this.currentSelectedCollection) {
-                this.doLoop(this.currentSelectedCollection, undefined);
+                this.setSearchDocsRecursive(this.currentSelectedCollection, undefined);
             }
             this.closeSearch(false);
 
             if (this.currentSelectedCollection !== undefined) {
-                this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
                 this.currentSelectedCollection = undefined;
                 this.props.Document.selectedDoc = undefined;
             }
@@ -242,14 +238,12 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             while (docs.length > 0) {
                 newarray = [];
                 docs.forEach((d) => {
-                    if (d.data !== undefined) {
-                        newarray.push(...DocListCast(d.data));
-                    }
-                    const hlights: string[] = [];
+                    d.data && newarray.push(...DocListCast(d.data));
+                    const hlights = new Set<string>();
                     this.documentKeys(d).forEach(key =>
-                        Field.toString(d[key] as Field).toLowerCase().includes(query) && !hlights.includes(key) && hlights.push(key));
-                    if (hlights.length > 0) {
-                        found.push([d, hlights, []]);
+                        Field.toString(d[key] as Field).toLowerCase().includes(query) && hlights.add(key));
+                    if (Array.from(hlights.keys()).length > 0) {
+                        found.push([d, Array.from(hlights.keys()), []]);
                         docsforFilter.push(d);
                     }
                 });
@@ -259,7 +253,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             this.docsforfilter = docsforFilter;
             if (this.filter === true) {
                 selectedCollection.props.Document._searchDocs = new List<Doc>(docsforFilter);
-                this.doLoop(selectedCollection, docsforFilter);
+                this.setSearchDocsRecursive(selectedCollection, docsforFilter);
             }
             this._numTotalResults = found.length;
             this.realTotalResults = found.length;
@@ -285,7 +279,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     @action
     submitSearch = async (reset?: boolean) => {
         if (this.currentSelectedCollection !== undefined) {
-            this.doLoop(this.currentSelectedCollection, undefined);
+            this.setSearchDocsRecursive(this.currentSelectedCollection, undefined);
         }
         if (reset) {
             this.layoutDoc._searchString = "";
@@ -434,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     closeSearch = (closesearchbar = true) => {
         this._results.forEach(result => {
             Doc.UnBrushDoc(result[0]);
-            result[0].searchMatch = undefined;
+            Doc.ClearSearchMatches();
         });
         closesearchbar && (this._searchbarOpen = false);
     }
@@ -479,9 +473,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                 const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
                 const lines = new List<string>(result[2]);
                 highlights.forEach((item) => headers.add(item));
-                result[0].lines = lines;
-                result[0].highlighting = highlights.join(", ");
-                result[0].searchMatch = true;
+                Doc.SetSearchMatch(result[0], { searchMatch: 1 });
                 if (i < this._visibleDocuments.length) {
                     this._visibleDocuments[i] = result[0];
                     Doc.BrushDoc(result[0]);
@@ -530,7 +522,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         this._searchFullDB = scope;
         this.dataDoc[this.fieldKey] = new List<Doc>([]);
         if (this.currentSelectedCollection !== undefined) {
-            this.doLoop(this.currentSelectedCollection, undefined);
+            this.setSearchDocsRecursive(this.currentSelectedCollection, undefined);
         }
         this.submitSearch();
     }
@@ -566,7 +558,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         </div>;
     }
 
-    doLoop = (collectionView: DocumentView, filter: Doc[] | undefined) => {
+    setSearchDocsRecursive = (collectionView: DocumentView, filter: Doc[] | undefined) => {
         let docs = DocListCast(collectionView.dataDoc[Doc.LayoutFieldKey(collectionView.dataDoc)]);
         let newarray: Doc[] = [];
         while (docs.length > 0) {
@@ -614,7 +606,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                                             onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => this.layoutDoc._searchString ? this.startDragCollection() : undefined); }}
                                             onClick={action(() => {
                                                 this.filter = !this.filter && !this._searchFullDB;
-                                                this.currentSelectedCollection && this.doLoop(this.currentSelectedCollection, this.filter ? this.docsforfilter : undefined);
+                                                this.currentSelectedCollection && this.setSearchDocsRecursive(this.currentSelectedCollection, this.filter ? this.docsforfilter : undefined);
                                             })} />
                                     </div>
                                 </Tooltip>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c78b01def..dc274a063 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -23,6 +23,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u
 import { LinkManager } from "../client/util/LinkManager";
 import JSZip = require("jszip");
 import { saveAs } from "file-saver";
+import { result } from "lodash";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -881,6 +882,7 @@ export namespace Doc {
 
     export class DocBrush {
         BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+        SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
     }
     const brushManager = new DocBrush();
 
@@ -906,6 +908,29 @@ export namespace Doc {
     export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
     export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; }
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
+
+    export function IsSearchMatch(doc: Doc) {
+        return computedFn(function IsSearchMatch(doc: Doc) {
+            return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+                brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+        })(doc);
+    }
+    export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        brushManager.SearchMatchDoc.set(doc, results);
+        return doc;
+    }
+    export function SearchMatchNext(doc: Doc, backward: boolean) {
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        const result = brushManager.SearchMatchDoc.get(doc);
+        const num = Math.abs(result?.searchMatch || 0) + 1;
+        runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
+        return doc;
+    }
+    export function ClearSearchMatches() {
+        brushManager.SearchMatchDoc.clear();
+    }
+
     export function IsBrushed(doc: Doc) {
         return computedFn(function IsBrushed(doc: Doc) {
             return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From ff5994377a80ec76c0138c5966f43a5bbce6e8b3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 22 Aug 2020 19:44:02 -0400
Subject: more search cleanup.  searches over dashboards, not collections now. 
 fixed drag-off filters to still work with collections.

---
 src/Utils.ts                                       |   2 +-
 src/client/documents/Documents.ts                  |   1 +
 .../views/collections/CollectionDockingView.tsx    |  27 ++--
 src/client/views/collections/CollectionMenu.tsx    |   6 +-
 .../views/collections/CollectionSchemaCells.tsx    |  12 +-
 src/client/views/collections/CollectionSubView.tsx |   1 +
 src/client/views/collections/SchemaTable.tsx       | 153 ++++++---------------
 src/client/views/nodes/KeyValuePair.tsx            |   5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/search/SearchBox.tsx              | 135 +++++-------------
 src/fields/Doc.ts                                  |   6 +-
 src/fields/List.ts                                 |   2 +-
 12 files changed, 114 insertions(+), 238 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 3ff05104f..5b5a06839 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -422,7 +422,7 @@ export function returnEmptyString() { return ""; }
 
 export function returnEmptyFilter() { return [] as string[]; }
 
-export function returnEmptyDoclist() { return [] as Doc[]; }
+export function returnEmptyDoclist() { return [] as any[]; }
 
 export let emptyPath = [];
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 49da4f9eb..36712ab3e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -873,6 +873,7 @@ export namespace DocUtils {
         }
 
         const filteredDocs = docFilters.length ? childDocs.filter(d => {
+            if (d.z) return true;
             for (const facetKey of Object.keys(filterFacets)) {
                 const facet = filterFacets[facetKey];
                 const satisfiesFacet = Object.keys(facet).some(value => {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 58c40b1d4..6277fbb14 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -30,14 +30,14 @@ import { DocumentView } from "../nodes/DocumentView";
 import { PresBox } from '../nodes/PresBox';
 import "./CollectionDockingView.scss";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { SubCollectionViewProps } from "./CollectionSubView";
+import { SubCollectionViewProps, CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
-export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
+export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @observable public static Instances: CollectionDockingView[] = [];
     @computed public static get Instance() { return CollectionDockingView.Instances[0]; }
     public static makeDocumentConfig(document: Doc, width?: number, libraryPath?: Doc[]) {
@@ -73,20 +73,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
-        let config: any;
-        if (dragDocs.length === 1) {
-            config = CollectionDockingView.makeDocumentConfig(dragDocs[0]);
-        } else {
-            config = {
+        const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
+            {
                 type: 'row',
                 content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc))
             };
-        }
-        const div = document.createElement("div");
-        const dragSource = this._goldenLayout.createDragSource(div, config);
-        dragSource._dragListener.on("dragStop", () => {
-            dragSource.destroy();
-        });
+        const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
+        dragSource._dragListener.on("dragStop", () => dragSource.destroy());
         dragSource._dragListener.onMouseDown(e);
     }
 
@@ -939,8 +932,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
                 addDocTab={this.addDocTab}
                 pinToPres={DockedFrameRenderer.PinDoc}
-                docFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
+                docFilters={CollectionDockingView.Instance.docFilters}
+                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
                 fitToBox={true}
             />
             <div className="miniOverlay" onPointerDown={this.miniDown} >
@@ -981,8 +974,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
                 addDocTab={this.addDocTab}
                 pinToPres={DockedFrameRenderer.PinDoc}
-                docFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
+                docFilters={CollectionDockingView.Instance.docFilters}
+                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
             {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5580c32f2..00415dab1 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -180,12 +180,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
     };
     _saveFilterCommand = {
         params: ["target"], title: "save filter",
-        script: `self.target._docFilters = copyField(self['target-docFilters']); 
+        script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']); 
                  self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
         immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }),
         initialize: (button: Doc) => {
-            button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : undefined;
-            button['target-searchFilterDocs'] = this.target._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(this.target._searchFilterDocs as any as ObjectField) : undefined;
+            button['target-docFilters'] = Cast(Doc.UserDoc()["search-panel"], Doc, null)._docFilters instanceof ObjectField ? ObjectField.MakeCopy(Cast(Doc.UserDoc()["search-panel"], Doc, null)._docFilters as any as ObjectField) : undefined;
+            button['target-searchFilterDocs'] = Cast(Doc.UserDoc()["search-panel"], Doc, null)._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(Cast(Doc.UserDoc()["search-panel"], Doc, null)._searchFilterDocs as any as ObjectField) : undefined;
         },
     };
 
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 2f1f7a90f..e37644f2f 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -3,7 +3,7 @@ import { action, observable, trace, computed, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { CellInfo } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter, Utils, emptyPath } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter, Utils, emptyPath, returnEmptyDoclist } from "../../../Utils";
 import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { KeyCodes } from "../../util/KeyCodes";
@@ -199,6 +199,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             rootSelected: returnFalse,
             fieldKey: this.props.rowProps.column.id as string,
             docFilters: returnEmptyFilter,
+            searchFilterDocs: returnEmptyDoclist,
             ContainingCollectionView: this.props.CollectionView,
             ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
             isSelected: returnFalse,
@@ -511,7 +512,8 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
         addDocTab: this.props.addDocTab,
         pinToPres: this.props.pinToPres,
         ContentScaling: returnOne,
-        docFilters: returnEmptyFilter
+        docFilters: returnEmptyFilter,
+        searchFilterDocs: returnEmptyDoclist,
     };
     @observable private _field = this.prop.Document[this.prop.fieldKey];
     @observable private _doc = FieldValue(Cast(this._field, Doc));
@@ -691,7 +693,8 @@ export class CollectionSchemaImageCell extends CollectionSchemaCell {
             addDocTab: this.props.addDocTab,
             pinToPres: this.props.pinToPres,
             ContentScaling: returnOne,
-            docFilters: returnEmptyFilter
+            docFilters: returnEmptyFilter,
+            searchFilterDocs: returnEmptyDoclist,
         };
 
         let image = true;
@@ -770,7 +773,8 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
         addDocTab: this.props.addDocTab,
         pinToPres: this.props.pinToPres,
         ContentScaling: returnOne,
-        docFilters: returnEmptyFilter
+        docFilters: returnEmptyFilter,
+        searchFilterDocs: returnEmptyDoclist,
     };
     @observable private _field = this.prop.Document[this.prop.fieldKey];
     @observable private _optionsList = this._field as List<any>;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c73159084..71e891045 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -136,6 +136,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             let docsforFilter: Doc[] = childDocs;
 
             if (searchDocs.length > 0) {
+                searchDocs = [...searchDocs, ...docs.filter(d => d.z)];
                 docsforFilter = [];
                 const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
                 searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript);
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index e1c81aeab..7de0b14ac 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -14,7 +14,6 @@ import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, returnEmptyDoclist } from "../../../Utils";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -22,11 +21,12 @@ import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
 import { ContextMenu } from "../ContextMenu";
 import '../DocumentDecorations.scss';
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
+import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell } from "./CollectionSchemaCells";
 import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
 import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
 import "./CollectionSchemaView.scss";
 import { CollectionView } from "./CollectionView";
+import { DocumentType } from "../../documents/DocumentTypes";
 
 
 enum ColumnType {
@@ -91,7 +91,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @observable _cellIsEditing: boolean = false;
     @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
-    @observable _openCollections: Array<string> = [];
+    @observable _openCollections: Set<number> = new Set;
 
     @observable _showDoc: Doc | undefined;
     @observable _showDataDoc: any = "";
@@ -136,16 +136,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @action
     changeSorting = (col: any) => {
-        if (col.desc === undefined) {
-            // no sorting
-            this.props.changeColumnSort(col, true);
-        } else if (col.desc === true) {
-            // descending sort
-            this.props.changeColumnSort(col, false);
-        } else if (col.desc === false) {
-            // ascending sort
-            this.props.changeColumnSort(col, undefined);
-        }
+        this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
     }
 
     @action
@@ -153,7 +144,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
     @computed get tableColumns(): Column<Doc>[] {
-
         const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
         const columns: Column<Doc>[] = [];
         const tableIsFocused = this.props.isFocused(this.props.Document, false);
@@ -161,27 +151,16 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         const focusedCol = this._focusedCell.col;
         const isEditable = !this.props.headerIsEditing;
 
-        //if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) {
-        columns.push(
-            {
-                expander: true,
-                Header: "",
-                width: 60,
-                Expander: (rowInfo) => {
-                    if (rowInfo.original.type === "collection") {
-                        return rowInfo.isExpanded ?
-                            <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"caret-down"} size="lg" /></div> :
-                            <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"caret-right"} size="lg" /></div>;
-                    } else {
-                        return null;
-                    }
-                }
+        columns.push({
+            expander: true, Header: "", width: 60,
+            Expander: (rowInfo) => {
+                return rowInfo.original.type !== DocumentType.COL ? (null) :
+                    <div className="collectionSchemaView-expander" onClick={action(() => (this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}>
+                        <FontAwesomeIcon icon={rowInfo.isExpanded ? "caret-down" : "caret-right"} size="lg" />
+                    </div>;
             }
-        );
-        // }
-        this.props.active;
-
-        const cols = this.props.columns.map(col => {
+        });
+        columns.push(...this.props.columns.map(col => {
             const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" :
                 this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" :
                     this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" :
@@ -209,25 +188,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                 width={"100%"}
             />;
 
-
-
             const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
-
-            const header =
-                <div //className="collectionSchemaView-header"
-                    //onClick={e => this.props.openHeader(col, menuContent, e.clientX, e.clientY)}
-                    className="collectionSchemaView-menuOptions-wrapper"
-                    style={{
-                        background: col.color, padding: "2px",
-                        display: "flex", cursor: "default", height: "100%",
-                    }}>
-                    {/* <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> */}
-                    {keysDropdown}
-                    <div onClick={e => this.changeSorting(col)}
-                        style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
-                        <FontAwesomeIcon icon={sortIcon} size="lg" />
-                    </div>
-                </div>;
+            const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
+                {keysDropdown}
+                <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
+                    <FontAwesomeIcon icon={sortIcon} size="lg" />
+                </div>
+            </div>;
 
             return {
                 Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
@@ -260,21 +227,20 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                         showDoc: this.showDoc,
                     };
 
-                    const colType = this.getColumnType(col);
-                    if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props} />;
-                    if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props} />;
-                    if (colType === ColumnType.Boolean) return <CollectionSchemaCheckboxCell {...props} />;
-                    if (colType === ColumnType.Doc) return <CollectionSchemaDocCell {...props} />;
-                    if (colType === ColumnType.Image) return <CollectionSchemaImageCell {...props} />;
-                    if (colType === ColumnType.List) return <CollectionSchemaListCell {...props} />;
-                    if (colType === ColumnType.Date) return <CollectionSchemaDateCell {...props} />;
-                    return <CollectionSchemaCell {...props} />;
+                    switch (this.getColumnType(col)) {
+                        case ColumnType.Number: return <CollectionSchemaNumberCell {...props} />;
+                        case ColumnType.String: return <CollectionSchemaStringCell {...props} />;
+                        case ColumnType.Boolean: return <CollectionSchemaCheckboxCell {...props} />;
+                        case ColumnType.Doc: return <CollectionSchemaDocCell {...props} />;
+                        case ColumnType.Image: return <CollectionSchemaImageCell {...props} />;
+                        case ColumnType.List: return <CollectionSchemaListCell {...props} />;
+                        case ColumnType.Date: return <CollectionSchemaDateCell {...props} />;
+                        default: return <CollectionSchemaCell {...props} />;
+                    }
                 },
                 minWidth: 200,
             };
-        });
-        columns.push(...cols);
-
+        }));
         columns.push({
             Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
             accessor: (doc: Doc) => 0,
@@ -283,8 +249,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                 const rowIndex = rowProps.index;
                 const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
                 const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
-                const props: CellProps = {
-                    row: rowIndex,
+                return <CollectionSchemaButtons  {...{
+                    row: rowProps.index,
                     col: columnIndex,
                     rowProps: rowProps,
                     isFocused: isFocused,
@@ -303,9 +269,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                     setComputed: this.setComputed,
                     getField: this.getField,
                     showDoc: this.showDoc,
-                };
-
-                return <CollectionSchemaButtons {...props} />;
+                }} />;
             },
             width: 28,
             resizable: false
@@ -316,12 +280,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     constructor(props: SchemaTableProps) {
         super(props);
-        // convert old schema columns (list of strings) into new schema columns (list of schema header fields)
-        const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
-        if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
-            const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
-            this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
-        } else if (this.props.Document._schemaHeaders === undefined) {
+        if (this.props.Document._schemaHeaders === undefined) {
             this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date),
             new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]);
         }
@@ -362,19 +321,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
         // TODO: editing border doesn't work :(
         return {
-            style: {
-                border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb"
-            }
+            style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" }
         };
     }
 
-    @action
-    onCloseCollection = (collection: Doc): void => {
-        const index = this._openCollections.findIndex(col => col === collection[Id]);
-        if (index > -1) this._openCollections.splice(index, 1);
-    }
-
-    @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
     @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
 
     @action
@@ -431,21 +381,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @action
     getColumnType = (column: SchemaHeaderField): ColumnType => {
-        // added functionality to convert old column type stuff to new column type stuff -syip
         if (column.type && column.type !== 0) {
             return column.type;
         }
         if (columnTypes.get(column.heading)) {
-            column.type = columnTypes.get(column.heading)!;
-            return columnTypes.get(column.heading)!;
-        }
-        const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
-        if (!typesDoc) {
-            column.type = ColumnType.Any;
-            return ColumnType.Any;
+            return column.type = columnTypes.get(column.heading)!;
         }
-        column.type = NumCast(typesDoc[column.heading]);
-        return NumCast(typesDoc[column.heading]);
+        return column.type = ColumnType.Any;
     }
 
     @undoBatch
@@ -474,11 +416,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     @computed
     get reactTable() {
         const children = this.childDocs;
-        const hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
-        const expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
-        const expanded = {};
-        //@ts-ignore
-        expandedRowsList.forEach(row => expanded[row] = true);
+        const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
+        const expanded: { [name: string]: any } = {};
+        Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true);
         const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
 
         return <ReactTable
@@ -496,17 +436,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             expanded={expanded}
             resized={this.resized}
             onResizedChange={this.props.onResizedChange}
-            SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
+            SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) :
                 <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
 
         />;
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
-            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
-        }
+        ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
     }
 
     getField = (row: number, col?: number) => {
@@ -588,9 +525,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     onOpenClick = () => {
-        if (this._showDoc) {
-            this.props.addDocTab(this._showDoc, "onRight");
-        }
+        this._showDoc && this.props.addDocTab(this._showDoc, "onRight");
     }
 
     getPreviewTransform = (): Transform => {
@@ -606,7 +541,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             {StrCast(this.props.Document._chromeStatus) !== "disabled" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
                 : undefined}
             {!this._showDoc ? (null) :
-                <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }}
+                <div className="collectionSchemaView-documentPreview"
                     style={{
                         position: "absolute", width: 150, height: 150,
                         background: "dimGray", display: "block", top: 0, left: 0,
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 4568a6b16..6dd85b7f5 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
 import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
@@ -56,7 +56,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             Document: this.props.doc,
             DataDoc: this.props.doc,
             LibraryPath: [],
-            docFilters:returnEmptyFilter,
+            docFilters: returnEmptyFilter,
+            searchFilterDocs: returnEmptyDoclist,
             ContainingCollectionView: undefined,
             ContainingCollectionDoc: undefined,
             fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5250a8f58..ba0ac6293 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -906,7 +906,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             search => {
                 search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms();
             },
-            { fireImmediately: Doc.IsSearchMatch(this.rootDoc) ? true : false });
+            { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
 
         this._disposers.record = reaction(() => this._recording,
             () => {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9c9900bfb..e93fc86b5 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from '../../../fields/FieldSymbols';
+import { Id, Copy } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -15,12 +15,11 @@ import { Docs } from '../../documents/Documents';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { SetupDrag } from '../../util/DragManager';
 import { SearchUtil } from '../../util/SearchUtil';
-import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
+import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionSchemaView, ColumnType } from "../collections/CollectionSchemaView";
 import { CollectionViewType } from '../collections/CollectionView';
 import { ViewBoxBaseComponent } from "../DocComponent";
-import { DocumentView } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import "./SearchBox.scss";
 
@@ -48,9 +47,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     private docsforfilter: Doc[] | undefined = [];
     private realTotalResults: number = 0;
-    private collectionRef = React.createRef<HTMLSpanElement>();
+    private collectionRef = React.createRef<HTMLDivElement>();
 
-    @observable _currentSelectedCollection: DocumentView | undefined = undefined;
     @observable _icons: string[] = this._allIcons;
     @observable _results: [Doc, string[], string[]][] = [];
     @observable _visibleElements: JSX.Element[] = [];
@@ -64,7 +62,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     @observable open = false;
     @observable children = 0;
     @observable newsearchstring = "";
-    @computed get filter() { return this._results?.length && (this._currentSelectedCollection?.props.Document._searchFilterDocs || this._currentSelectedCollection?.props.Document._docFilters); }
+    @computed get filter() { return this._results?.length && (this.currentSelectedCollection?.props.Document._searchFilterDocs || this.currentSelectedCollection?.props.Document._docFilters); }
 
     constructor(props: any) {
         super(props);
@@ -77,25 +75,24 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             this._searchbarOpen = true;
         }
         this._disposers.filters = reaction(() => this.props.Document._docFilters,
-            (filters: any) => this.setSearchFilter(this._currentSelectedCollection, this.filter ? undefined : this.docsforfilter));
+            (filters: any) => this.setSearchFilter(this.currentSelectedCollection, !this.filter ? undefined : this.docsforfilter));
     });
 
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
+    @computed get currentSelectedCollection() { return CollectionDockingView.Instance; }
+
     @action.bound
     onChange(e: React.ChangeEvent<HTMLInputElement>) {
         this.layoutDoc._searchString = e.target.value;
         this.newsearchstring = e.target.value;
         if (e.target.value === "") {
             this.docsforfilter = undefined;
-            this.setSearchFilter(this._currentSelectedCollection, undefined);
+            this.setSearchFilter(this.currentSelectedCollection, undefined);
             this.resetSearch(false);
 
-            if (this._currentSelectedCollection !== undefined) {
-                this._currentSelectedCollection = undefined;
-            }
             this.open = false;
             this._results = [];
             this._resultsSet.clear();
@@ -149,9 +146,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                 const mod = "_t:";
                 const newWords: string[] = [];
                 const oldWords = values[0].split(" ");
-                oldWords.forEach((word, i) => {
-                    i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"");
-                });
+                oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
                 query = `(${query}) AND (${newWords.join(" ")})`;
             }
             else {
@@ -159,22 +154,12 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                     const mod = "_t:";
                     const newWords: string[] = [];
                     const oldWords = values[i].split(" ");
-                    oldWords.forEach((word, i) => {
-                        i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"");
-                    });
+                    oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
                     const v = "(" + newWords.join(" ") + ")";
                     if (i === 0) {
-                        query = `(${query}) AND (${v}`;
-                        if (values.length === 1) {
-                            query = query + ")";
-                        }
-                    }
-                    else if (i === values.length - 1) {
-                        query = query + " OR " + v + ")";
-                    }
-                    else {
-                        query = query + " OR " + v;
+                        query = `(${query}) AND (${v}` + (values.length === 1 ? ")" : "");
                     }
+                    else query = query + " OR " + v + (i === values.length - 1 ? ")" : "");
                 }
             }
         }
@@ -194,40 +179,15 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         return finalDocs;
     }
 
-    //TODO: basically all of this
-    //gets all of the collections of all the docviews that are selected
-    //if a collection is the only thing selected, search only in that collection (not its container)
-    getCurCollections(): Doc[] {
-        const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments();
-        const collections: Doc[] = [];
-        selectedDocs.forEach(async element => {
-            const layout: string = StrCast(element.props.Document.layout);
-            //checks if selected view (element) is a collection. if it is, adds to list to search through
-            if (layout.indexOf("Collection") > -1) {
-                //makes sure collections aren't added more than once
-                if (!collections.includes(element.props.Document)) {
-                    collections.push(element.props.Document);
-                }
-            }
-            //makes sure collections aren't added more than once
-            if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) {
-                collections.push(element.props.ContainingCollectionDoc);
-            }
-        });
-
-        return collections;
-    }
-
-
+    @action
     searchCollection(query: string) {
-        const selectedCollection = SelectionManager.SelectedDocuments()[0];
+        const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
         query = query.toLowerCase();
 
         if (selectedCollection !== undefined) {
-            this._currentSelectedCollection = selectedCollection;
+            // this._currentSelectedCollection = selectedCollection;
             let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
             const found: [Doc, string[], string[]][] = [];
-            const docsforFilter: Doc[] = [];
             let newarray: Doc[] = [];
 
             while (docs.length > 0) {
@@ -239,14 +199,13 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                         Field.toString(d[key] as Field).toLowerCase().includes(query) && hlights.add(key));
                     if (Array.from(hlights.keys()).length > 0) {
                         found.push([d, Array.from(hlights.keys()), []]);
-                        docsforFilter.push(d);
                     }
                 });
                 docs = newarray;
             }
             this._results = found;
-            this.docsforfilter = docsforFilter;
-            this.setSearchFilter(selectedCollection, this.filter && found.length ? docsforFilter : undefined);
+            this.docsforfilter = this._results.map(r => r[0]);
+            this.setSearchFilter(selectedCollection, this.filter && found.length ? this.docsforfilter : undefined);
             this._numTotalResults = found.length;
             this.realTotalResults = found.length;
         }
@@ -285,11 +244,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         this._visibleElements = [];
         this._visibleDocuments = [];
 
-        if (query !== "" || this._searchFullDB === "My Stuff") {
+        if (query || this._searchFullDB === "My Stuff") {
             this._endIndex = 12;
             this._maxSearchIndex = 0;
             this._numTotalResults = -1;
-            this._searchFullDB ? await this.getResults(query) : this.searchCollection(query);
+            this._searchFullDB ? await this.searchDatabase(query) : this.searchCollection(query);
             runInAction(() => {
                 this._searchbarOpen = true;
                 this.resultsScrolled();
@@ -307,8 +266,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         const includeDeleted = this._deletedDocsStatus ? "" : " NOT deleted_b:true";
         const typeExpr = this._onlyAliases ? "NOT {!join from=id to=proto_i}type_t:*" : `(type_t:* OR {!join from=id to=proto_i}type_t:*) ${this._blockedTypes.map(type => `NOT ({!join from=id to=proto_i}type_t:${type}) AND NOT type_t:${type}`).join(" AND ")}`;
         // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection   q:text_t:hello
-        const query = [baseExpr, authorExpr, includeDeleted, typeExpr].filter(q => q).join(" AND ").replace(/AND $/, "");
-        return query;
+        return [baseExpr, authorExpr, includeDeleted, typeExpr].filter(q => q).join(" AND ").replace(/AND $/, "");
     }
 
     @computed get primarySort() {
@@ -324,7 +282,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         return headers.reduce((p: Opt<string>, header: SchemaHeaderField) => p || (header.desc !== undefined && suffixMap(header.type) ? (header.heading + suffixMap(header.type) + (header.desc ? " desc" : " asc")) : undefined), undefined);
     }
 
-    getResults = async (query: string) => {
+    searchDatabase = async (query: string) => {
         this._lockPromise && (await this._lockPromise);
         this._lockPromise = new Promise(async res => {
             while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
@@ -368,6 +326,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             }
 
             this.resultsScrolled();
+
+            const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
+            this.docsforfilter = this._results.map(r => r[0]);
+            this.setSearchFilter(selectedCollection, this.filter ? this.docsforfilter : undefined);
             res();
         });
         return this._lockPromise;
@@ -400,7 +362,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                 y += 300;
             }
         }
-        return Docs.Create.SchemaDocument(Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []), DocListCast(this.dataDoc[this.fieldKey]), { _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString) });
+        const headers = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).map(h => { const v = h[Copy](); v.color = "#f1efeb"; return v; });
+        return Docs.Create.SchemaDocument(headers, DocListCast(this.dataDoc[this.fieldKey]), { _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString) });
     }
 
     @action.bound
@@ -416,9 +379,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             Doc.UnBrushDoc(result[0]);
             Doc.ClearSearchMatches();
         });
-        if (close) {
-            this._searchbarOpen = false;
-        }
+        close && (this._searchbarOpen = false);
     }
 
     @action.bound
@@ -470,16 +431,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                 }
             }
         }
-        const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
-        if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
-            const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
-            headers.forEach(header => {
-                if (oldSchemaHeaders.includes(header) === false) {
-                    newSchemaHeaders.push(new SchemaHeaderField(header, "#f1efeb"));
-                }
-            });
-            this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
-        } else if (this.props.Document._schemaHeaders === undefined) {
+        if (this.props.Document._schemaHeaders === undefined) {
             this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
         }
         if (this._maxSearchIndex >= this._numTotalResults) {
@@ -488,44 +440,29 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         }
     }
 
-    findCommonElements(arr2: string[]) {
-        const arr1 = ["layout", "data"];
-        return arr1.some(item => arr2.includes(item));
-    }
-
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight();
-
-    selectElement = (doc: Doc) => {
-        //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex));
-    }
+    selectElement = (doc: Doc) => { /* this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); */ }
     returnHeight = () => 31 + 31 * 6;
     returnLength = () => Math.min(window.innerWidth, 51 + 205 * Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length);
 
     @action
     changeSearchScope = (scope: string) => {
         this.docsforfilter = undefined;
-        this.setSearchFilter(this._currentSelectedCollection, undefined);
+        this.setSearchFilter(this.currentSelectedCollection, undefined);
         this._searchFullDB = scope;
         this.dataDoc[this.fieldKey] = new List<Doc>([]);
         this.submitSearch();
     }
 
     @computed get scopeButtons() {
-        return <div style={{
-            height: 25,
-            paddingLeft: "4px",
-            paddingRight: "4px",
-            border: "1px solid gray",
-            borderRadius: "0.3em",
-            borderBottom: !this.open ? "1px solid" : "none",
-        }}>
+        return <div style={{ height: 25, paddingLeft: "4px", paddingRight: "4px", border: "1px solid gray", borderRadius: "0.3em", borderBottom: !this.open ? "1px solid" : "none", }}>
             <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}>
                 <div style={{ display: "contents" }}>
                     <div className="radio" style={{ margin: 0 }}>
                         <label style={{ fontSize: 12, marginTop: 6 }} >
                             <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this._searchFullDB} onChange={() => this.changeSearchScope("")} />
-                            Collection
+                            Dashboard
                         </label>
                     </div>
                     <div className="radio" style={{ margin: 0 }}>
@@ -542,7 +479,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         </div>;
     }
 
-    setSearchFilter = (collectionView: DocumentView | undefined, docsForFilter: Doc[] | undefined) => {
+    setSearchFilter = (collectionView: { props: { Document: Doc } }, docsForFilter: Doc[] | undefined) => {
         if (collectionView) {
             const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), null);
             collectionView.props.Document._searchFilterDocs = docsForFilter?.length ? new List<Doc>(docsForFilter) : undefined;
@@ -561,7 +498,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                         <div style={{ display: "flex", alignItems: "center" }}>
                             <div style={{ position: "absolute", left: 10 }}>
                                 <Tooltip title={<div className="dash-tooltip" >drag search results as collection</div>}>
-                                    <div><FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg"
+                                    <div ref={this.collectionRef}><FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg"
                                         style={{ cursor: "hand", color: "black", padding: 1, position: "relative" }} /></div>
                                 </Tooltip>
                             </div>
@@ -574,7 +511,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                                         <FontAwesomeIcon icon={"filter"} size="lg"
                                             style={{ cursor: "hand", padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }}
                                             onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => this.layoutDoc._searchString ? this.startDragCollection() : undefined); }}
-                                            onClick={action(() => this.setSearchFilter(this._currentSelectedCollection, this.filter ? undefined : this.docsforfilter))} />
+                                            onClick={action(() => this.setSearchFilter(this.currentSelectedCollection, this.filter ? undefined : this.docsforfilter))} />
                                     </div>
                                 </Tooltip>
                             </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index af2ea73d5..c2b491454 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -915,6 +915,10 @@ export namespace Doc {
                 brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
         })(doc);
     }
+    export function IsSearchMatchUnmemoized(doc: Doc) {
+        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+            brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+    }
     export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.SearchMatchDoc.set(doc, results);
@@ -1276,7 +1280,7 @@ Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) {
         Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
     }
     return ndoc;
-})
+});
 Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
 Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 1c2b89b01..3601f282b 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -334,5 +334,5 @@ Scripting.addGlobal("List", List);
 Scripting.addGlobal(function compareLists(l1: any, l2: any) {
     const L1 = Cast(l1, listSpec("string"), []);
     const L2 = Cast(l2, listSpec("string"), []);
-    return L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
+    return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
 }, "compare two lists");
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6048484932b9abe2962712bf2b8af56eeaab1176 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 22 Aug 2020 21:08:56 -0400
Subject: changed customTitle to title-custom

---
 src/client/views/DocumentDecorations.tsx                  | 6 +++---
 src/client/views/collections/CollectionSchemaHeaders.tsx  | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 src/client/views/nodes/formattedText/RichTextRules.ts     | 2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c44c2968c..8e44bcf8f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -117,7 +117,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 this._titleControlString = this._accumulatedTitle;
             } else if (this._titleControlString.startsWith("#")) {
                 const selectionTitleFieldKey = this._titleControlString.substring(1);
-                selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-"));
+                selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"));
                 UndoManager.RunInBatch(() => selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d => {
                     const value = typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle;
                     Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, value, true);
@@ -624,8 +624,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
                 {minimal ? (null) : <div className="publishBox" // title="make document referenceable by its title"
                 // onPointerDown={action(e => {
-                //     if (!seldoc.props.Document.customTitle) {
-                //         seldoc.props.Document.customTitle = true;
+                //     if (!seldoc.props.Document["title-custom"]) {
+                //         seldoc.props.Document["title-custom"] = true;
                 //         StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
                 //         this._accumulatedTitle = StrCast(seldoc.props.Document.title);
                 //     }
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index a90d78a87..03a213002 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -302,7 +302,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
                 Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined);
                 this.updateFilter();
                 let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
-                const blockedkeys = ["system", "customTitle", "limitHeight", "proto", "x", "y", "zIndex", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
+                const blockedkeys = ["system", "title-custom", "limitHeight", "proto", "x", "y", "zIndex", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
                 keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL"));
                 if (keyOptions.length) {
                     this.onSelect(keyOptions[0]);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ba0ac6293..7a6d263f9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -350,7 +350,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     updateTitle = () => {
         if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
+            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"]) {
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
             const str = node.textContent;
@@ -1029,7 +1029,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }, 0);
             dataDoc.title = exportState.title;
-            this.rootDoc.customTitle = true;
+            this.dataDoc["title-custom"] = true;
             dataDoc.unchanged = true;
         } else {
             delete dataDoc[GoogleRef];
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index dc1d8a2c8..a455516a3 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -92,7 +92,7 @@ export class RichTextRules {
                     const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
                     const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
                     textDocInline.title = inlineFieldKey; // give the annotation its own title
-                    textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+                    textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                     textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
                     textDocInline.proto = textDoc;  // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
                     textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
-- 
cgit v1.2.3-70-g09d2


From f504934782b6458585148e3412b5fef975515931 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Aug 2020 16:26:28 -0400
Subject: added settings for default text titles.  fixed image audio
 annotatoins. fixed captions to not set title.  added properties buttons for
 layout options and removed template layout button.  fixed warnings.

---
 src/client/documents/Documents.ts                  |   6 +-
 src/client/util/DictationManager.ts                |  19 ++-
 src/client/util/SettingsManager.tsx                |   7 +-
 src/client/views/DocumentButtonBar.tsx             |  17 ++-
 src/client/views/EditableView.tsx                  |   5 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PreviewCursor.tsx                 |   1 +
 src/client/views/PropertiesButtons.scss            |   7 +
 src/client/views/PropertiesButtons.tsx             | 144 +++++++++++++++------
 src/client/views/TemplateMenu.tsx                  |   6 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   4 +-
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 src/client/views/collections/SchemaTable.tsx       |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  71 +++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx |   3 +-
 .../collectionFreeForm/PropertiesView.scss         |   3 +-
 .../collectionFreeForm/PropertiesView.tsx          |   4 +-
 .../collectionGrid/CollectionGridView.tsx          |   2 +-
 src/client/views/linking/LinkMenu.tsx              |   1 -
 src/client/views/nodes/AudioBox.tsx                |   6 +-
 src/client/views/nodes/DocumentView.scss           |   7 +
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  28 ++--
 src/client/views/nodes/PresBox.tsx                 |   2 +-
 .../nodes/formattedText/FormattedTextBox.scss      |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 103 ++++++---------
 src/client/views/search/SearchBox.tsx              |   2 +-
 28 files changed, 260 insertions(+), 204 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 36712ab3e..cce50e306 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -988,7 +988,7 @@ export namespace DocUtils {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
             layout = CollectionView.LayoutString;
         } else {
-            created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
+            created = Docs.Create.TextDocument("", { ...{ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 25, _autoHeight: true }, ...resolved });
             layout = FormattedTextBox.LayoutString;
         }
         if (created) {
@@ -1048,7 +1048,7 @@ export namespace DocUtils {
                 description: ":" + StrCast(note.title),
                 event: undoBatch((args: { x: number, y: number }) => {
                     const textDoc = Docs.Create.TextDocument("", {
-                        _width: 200, x, y, _autoHeight: note._autoHeight !== false,
+                        _width: 200, x, y, _autoHeight: note._autoHeight !== false, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
                         title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
                     });
                     textDoc.layoutKey = "layout_" + note.title;
@@ -1175,7 +1175,7 @@ export namespace DocUtils {
         }
         const options = optionsCollection as Doc;
         const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
-        const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
+        const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
         targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
         targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
         targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 540540642..e66c15fcb 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -6,7 +6,6 @@ import { DocumentType } from "../documents/DocumentTypes";
 import { Doc, Opt } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { Docs } from "../documents/Documents";
-import { CollectionViewType } from "../views/collections/CollectionView";
 import { Cast, CastCtor } from "../../fields/Types";
 import { listSpec } from "../../fields/Schema";
 import { AudioField, ImageField } from "../../fields/URLField";
@@ -88,7 +87,7 @@ export namespace DictationManager {
         export const listen = async (options?: Partial<ListeningOptions>) => {
             let results: string | undefined;
 
-            const overlay = options !== undefined && options.useOverlay;
+            const overlay = options?.useOverlay;
             if (overlay) {
                 DictationOverlay.Instance.dictationOverlayVisible = true;
                 DictationOverlay.Instance.isListening = { interim: false };
@@ -100,11 +99,11 @@ export namespace DictationManager {
                     Utils.CopyText(results);
                     if (overlay) {
                         DictationOverlay.Instance.isListening = false;
-                        const execute = options && options.tryExecute;
+                        const execute = options?.tryExecute;
                         DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
                         DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
                     }
-                    options && options.tryExecute && await DictationManager.Commands.execute(results);
+                    options?.tryExecute && await DictationManager.Commands.execute(results);
                 }
             } catch (e) {
                 if (overlay) {
@@ -129,12 +128,12 @@ export namespace DictationManager {
             }
             isListening = true;
 
-            const handler = options ? options.interimHandler : undefined;
-            const continuous = options ? options.continuous : undefined;
+            const handler = options?.interimHandler;
+            const continuous = options?.continuous;
             const indefinite = continuous && continuous.indefinite;
-            const language = options ? options.language : undefined;
-            const intra = options && options.delimiters ? options.delimiters.intra : undefined;
-            const inter = options && options.delimiters ? options.delimiters.inter : undefined;
+            const language = options?.language;
+            const intra = options?.delimiters?.intra;
+            const inter = options?.delimiters?.inter;
 
             recognizer.onstart = () => console.log("initiating speech recognition session...");
             recognizer.interimResults = handler !== undefined;
@@ -154,7 +153,7 @@ export namespace DictationManager {
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
                     current = synthesize(e, intra);
                     let matchedTerminator: string | undefined;
-                    if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+                    if (options?.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
                         current = matchedTerminator;
                         recognizer.abort();
                         return complete();
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index accb9c346..bc5e1e9d3 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -54,6 +54,7 @@ export default class SettingsManager extends React.Component<{}> {
     }
 
     @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice");
+    @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value);
     @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
     @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
     @undoBatch switchColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex));
@@ -90,13 +91,17 @@ export default class SettingsManager extends React.Component<{}> {
                 {colorFlyout}
             </div>
             <div className="preferences-font">
-                <div className="preferences-font-text">Default Font</div>
+                <div className="preferences-font-text">Font</div>
                 <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
                     {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
                 </select>
                 <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}>
                     {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
                 </select>
+                <div>
+                    Show title
+                    <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = !Doc.UserDoc().showTitle} checked={BoolCast(Doc.UserDoc().showTitle)} />
+                </div>
             </div>
         </div>;
     }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 82ece7dc3..6e452e52d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -24,6 +24,7 @@ import React = require("react");
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { Tooltip } from '@material-ui/core';
 import SharingManager from '../util/SharingManager';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -216,6 +217,17 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </div></Tooltip >;
     }
 
+    @computed
+    get moreButton() {
+        const targetDoc = this.view0?.props.Document;
+        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Properties Panel"}</div></>}>
+            <div className="documentButtonBar-linker" style={{ color: "white" }} onClick={action(e =>
+                CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="ellipsis-h"
+                />
+            </div></Tooltip >;
+    }
+
     @computed
     get metadataButton() {
         const view0 = this.view0;
@@ -304,8 +316,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 {this.pinButton}
             </div>
-            <div className="documentButtonBar-button">
+            {/* <div className="documentButtonBar-button">
                 {this.shareButton}
+            </div> */}
+            <div className="documentButtonBar-button">
+                {this.moreButton}
             </div>
             {/* <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
                 {this.considerGoogleDocsPush}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ec3e754fb..b96802f06 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -227,7 +227,10 @@ export class EditableView extends React.Component<EditableProps> {
                     ref={this._ref}
                     style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
                     onClick={this.onClick} placeholder={this.props.placeholder}>
-                    {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ? <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+                    {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ?
+                        <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{//}, color: this.props.contents ? "black" : "grey" }}>{
+                            this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+                        </span>
                         : this.returnHighlights()}
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ccb051f6d..f8be58f8d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -182,7 +182,7 @@ export class MainView extends React.Component {
             fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle,
             fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
             fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
-            fa.faWindowMinimize, fa.faWindowRestore);
+            fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faClosedCaptioning);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 0eee46275..5d8f30354 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -92,6 +92,7 @@ export class PreviewCursor extends React.Component<{}> {
                         _width: 500,
                         limitHeight: 400,
                         _autoHeight: true,
+                        _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
                         x: newPoint[0],
                         y: newPoint[1],
                         title: "-pasted text-"
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index 8d9d56c9e..d63eb874c 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -43,6 +43,13 @@ $linkGap : 3px;
         cursor: pointer;
     }
 }
+.propertiesButtons-linkButton-empty.toggle-on {
+    background-color: white;
+    color: black;
+}
+.propertiesButtons-linkButton-empty.toggle-off {
+    color: white;
+}
 
 .propertiesButtons {
     margin-top: 3px;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index bcb492c49..ecd0ee37e 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -219,18 +219,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
         return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
             <div>
-                <div className="propertiesButtons-linker"
-                    style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }}
-                    onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
+                <div className={`propertiesButtons-linkButton-empty toggle-${isPinned ? "on" : "off"}`} onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
                 </div>
 
-                <div className="propertiesButtons-title"
-                // style={{
-                //     backgroundColor: Doc.isDocPinned(targetDoc) ? "white" : "black",
-                //     color: Doc.isDocPinned(targetDoc) ? "black" : "white"
-                // }}
-                >{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div>
+                <div className="propertiesButtons-title">{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div>
             </div>
         </Tooltip>;
     }
@@ -375,17 +368,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return !targetDoc ? (null) : <Tooltip
             title={<div className="dash-tooltip">{`${this.selectedDoc?.lockedPosition ? "Unlock" : "Lock"} " Position"`}</div>} placement="top">
             <div>
-                <div className={"propertiesButtons-linkButton-empty"} onPointerDown={this.onLock}
-                    style={{ backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "" }} >
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc.lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} >
                     <FontAwesomeIcon className="documentdecorations-icon" size="lg"
                         color={this.selectedDoc?.lockedPosition ? "black" : "white"}
                         icon={this.selectedDoc?.lockedPosition ? "unlock" : "lock"} />
                 </div>
                 <div className="propertiesButtons-title"
-                // style={{
-                //     backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "black",
-                //     color: BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"
-                // }}
                 >Position </div>
             </div>
         </Tooltip>;
@@ -426,12 +414,88 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         SelectionManager.DeselectAll();
     }
 
+    @undoBatch
+    @action
+    setDictation = () => {
+        this.selectedDoc && (this.selectedDoc._showAudio = !this.selectedDoc._showAudio);
+    }
+
+    @computed
+    get dictationButton() {
+        const targetDoc = this.selectedDoc;
+        return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Dictation Controls"}</div>} placement="top">
+            <div>
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showAudio ? "on" : "off"}`} onPointerDown={this.setDictation}>
+                    <FontAwesomeIcon className="propertiesButtons-icon" icon="microphone" size="lg" />
+                </div>
+                <div className="propertiesButtons-title"> Dictate </div>
+            </div>
+        </Tooltip>;
+    }
+
+
+    @undoBatch
+    @action
+    setTitle = () => {
+        this.selectedDoc && (this.selectedDoc._showTitle = this.selectedDoc._showTitle === undefined ? "title" : undefined);
+    }
+
+    @computed
+    get titleButton() {
+        const targetDoc = this.selectedDoc;
+        return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Title Header"}</div>} placement="top">
+            <div>
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showTitle ? "on" : "off"}`} onPointerDown={this.setTitle}>
+                    <FontAwesomeIcon className="propertiesButtons-icon" icon="text-width" size="lg" />
+                </div>
+                <div className="propertiesButtons-title"> Title </div>
+            </div>
+        </Tooltip>;
+    }
+
+    @undoBatch
+    @action
+    setCaption = () => {
+        this.selectedDoc && (this.selectedDoc._showCaption = this.selectedDoc._showCaption === undefined ? "caption" : undefined);
+    }
+
+    @computed
+    get captionButton() {
+        const targetDoc = this.selectedDoc;
+        return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Caption Footer"}</div>} placement="top">
+            <div>
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showCaption ? "on" : "off"}`} onPointerDown={this.setCaption}>
+                    <FontAwesomeIcon className="propertiesButtons-icon" icon="closed-captioning" size="lg" />
+                </div>
+                <div className="propertiesButtons-title"> Caption </div>
+            </div>
+        </Tooltip>;
+    }
+
+    @undoBatch
+    @action
+    setChrome = () => {
+        this.selectedDoc && (this.selectedDoc._chromeStatus = this.selectedDoc._chromeStatus === "disabled" ? "enabled" : "disabled");
+    }
+
+    @computed
+    get chromeButton() {
+        const targetDoc = this.selectedDoc;
+        return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Editing UI"}</div>} placement="top">
+            <div>
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._chromeStatus === "enabled" ? "on" : "off"}`} onPointerDown={this.setChrome}>
+                    <FontAwesomeIcon className="propertiesButtons-icon" icon="edit" size="lg" />
+                </div>
+                <div className="propertiesButtons-title"> Controls </div>
+            </div>
+        </Tooltip>;
+    }
+
     @computed
     get sharingButton() {
         const targetDoc = this.selectedDoc;
         const docView = this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined;
-        return !targetDoc ? (null) : <Tooltip
-            title={<><div className="dash-tooltip">{"Share Document"}</div></>} placement="top">
+        return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Share Document"}</div>} placement="top">
             <div>
                 <div className={"propertiesButtons-linkButton-empty"} onPointerDown={() => this.selectedDocumentView && SharingManager.Instance.open(docView, this.selectedDoc)}>
                     <FontAwesomeIcon className="propertiesButtons-icon" icon="users" size="lg" />
@@ -561,19 +625,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return !targetDoc ? (null) : <Tooltip
             title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top">
             <div>
-                <div className={"propertiesButtons-linkButton-empty"}
-                    style={{ backgroundColor: this.selectedDoc?.useClusters ? "white" : "" }}
-                    onPointerDown={this.changeClusters}>
-                    {<FontAwesomeIcon className="documentdecorations-icon"
-                        color={this.selectedDoc?.useClusters ? "black" : "white"}
-                        icon="braille" size="lg" />}
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc.userClusters ? "on" : "off"}`} onPointerDown={this.changeClusters}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="braille" size="lg" />
                 </div>
-                <div className="propertiesButtons-title"
-                // style={{
-                //     backgroundColor: this.selectedDoc?.useClusters ? "white" : "black",
-                //     color: this.selectedDoc?.useClusters ? "black" : "white"
-                // }}
-                > clusters </div>
+                <div className="propertiesButtons-title" > clusters </div>
             </div>
         </Tooltip>;
     }
@@ -594,19 +649,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return !targetDoc ? (null) : <Tooltip
             title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>} placement="top">
             <div>
-                <div className={"propertiesButtons-linkButton-empty"}
-                    style={{ backgroundColor: this.selectedDoc?._fitToBox ? "white" : "" }}
-                    onPointerDown={this.changeFitToBox}>
-                    {<FontAwesomeIcon className="documentdecorations-icon"
-                        color={this.selectedDoc?._fitToBox ? "black" : "white"}
-                        icon="expand" size="lg" />}
+                <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitToBox ? "on" : "off"}`} onPointerDown={this.changeFitToBox}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="expand" size="lg" />
                 </div>
-                <div className="propertiesButtons-title"
-                // style={{
-                //     backgroundColor: this.selectedDoc?._fitToBox ? "white" : "black",
-                //     color: this.selectedDoc?._fitToBox ? "black" : "white"
-                // }}
-                > {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
+                <div className="propertiesButtons-title"> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
             </div>
         </Tooltip>;
     }
@@ -690,9 +736,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const collectionAcl = GetEffectiveAcl(this.selectedDocumentView?.props.ContainingCollectionDoc?.[DataSym]);
 
         return <div><div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}>
-            <div className="propertiesButtons-button">
+            {/* <div className="propertiesButtons-button">
                 {this.templateButton}
-            </div>
+            </div> */}
             {/* <div className="propertiesButtons-button">
                 {this.metadataButton}
             </div> */}
@@ -705,9 +751,21 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             <div className="propertiesButtons-button" style={{ display: hasContext ? "" : "none" }}>
                 {this.copyButton}
             </div>
+            <div className="propertiesButtons-button">
+                {this.titleButton}
+            </div>
+            <div className="propertiesButtons-button">
+                {this.captionButton}
+            </div>
+            <div className="propertiesButtons-button" style={{ display: isCollection ? "" : "none" }}>
+                {this.chromeButton}
+            </div>
             <div className="propertiesButtons-button">
                 {this.lockButton}
             </div>
+            <div className="propertiesButtons-button" style={{ display: isText || isImage ? "" : "none" }}>
+                {this.dictationButton}
+            </div>
             <div className="propertiesButtons-button">
                 {this.downloadButton}
             </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e4ba45648..870af03aa 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -116,9 +116,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const addedTypes = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
-        this.props.templates.forEach((checked, template) =>
-            templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
-        templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
+        //this.props.templates.forEach((checked, template) =>
+        //    templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+        //templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
         addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index c772dcfe7..1c96f69bf 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -140,7 +140,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     addDocument = (value: string, shiftDown?: boolean) => {
         this._createAliasSelected = false;
         const key = StrCast(this.props.parent.props.Document._pivotField);
-        const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value });
+        const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value });
         newDoc[key] = this.getValue(this.props.heading);
         const docs = this.props.parent.childDocList;
         return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument(newDoc);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index ede75fba8..0a206a6c6 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -136,7 +136,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     addDocument = (value: string, shiftDown?: boolean) => {
         if (!value) return false;
         const key = StrCast(this.props.parent.props.Document._pivotField);
-        const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
+        const newDoc = Docs.Create.TextDocument(value, { _height: 18, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value, _autoHeight: true });
         newDoc[key] = this.getValue(this.props.heading);
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
         const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -269,7 +269,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
         ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
             Doc.GetProto(this.props.parent.props.Document)[name] = "";
-            const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+            const created = Docs.Create.TextDocument("", { title: name, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 250, _autoHeight: true });
             if (created) {
                 if (this.props.parent.Document.isTemplateDoc) {
                     Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 71e891045..cb3f486bb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -284,7 +284,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                             this.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
                         }
                     } else if (text) {
-                        this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
+                        this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 25 }));
                     }
                     return;
                 }
@@ -460,7 +460,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 completed?.();
             } else {
                 if (text && !text.includes("https://")) {
-                    UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
+                    UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
                 }
             }
             disposer();
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 7de0b14ac..4dafa4ac4 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -363,7 +363,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @undoBatch
     createRow = action(() => {
-        this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
+        this.props.addDocument(Docs.Create.TextDocument("", { title: "", _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 30 }));
         this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
     });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0c0f6ca58..206d04cf7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1045,7 +1045,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else if (viewDef.type === "div") {
             return [x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
+                    ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z + viewDef.payload} onClick={e => this.onViewDefDivClick(e, viewDef)}
                         style={{ width, height, backgroundColor: color, transform }} />,
                     bounds: viewDef
                 };
@@ -1597,18 +1597,15 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
             const vfWidth: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-width-indexed"]);
             const vfTop: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-top-indexed"]);
             const vfHeight: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-height-indexed"]);
-            return (
-                <>
-                    {!activeItem.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
-                        <div className='resizers'>
-                            <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerDown}></div>
-                            <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerDown}></div>
-                            <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerDown}></div>
-                            <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerDown}></div>
-                        </div>
-                    </div>}
-                </>
-            );
+            return !activeItem.editZoomProgressivize ? (null) :
+                <div id="resizable" key="zoomProgressivize" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
+                    <div className='resizers'>
+                        <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerDown}></div>
+                        <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerDown}></div>
+                        <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerDown}></div>
+                        <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerDown}></div>
+                    </div>
+                </div>;
         }
     }
 
@@ -1622,30 +1619,30 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
 
     @computed get presPaths() {
         const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden");
-        return !(PresBox.Instance) ? (null) : (<>
-            {!this.props.presPaths ? (null) : <><div>{PresBox.Instance.order}</div>
-                <svg className={presPaths}>
-                    <defs>
-                        <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
-                            <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" />
-                        </marker>
-                        <marker id="square" markerWidth="3" markerHeight="3" overflow="visible"
-                            refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
-                            <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" />
-                        </marker>
-                        <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4"
-                            orient="auto" overflow="visible">
-                            <rect x="1" y="1" width="5" height="5" fill="#69a6db" />
-                        </marker>
-
-                        <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7"
-                            orient="auto" overflow="visible">
-                            <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" />
-                        </marker>
-                    </defs>;
-                    {PresBox.Instance.paths}
-                </svg></>}
-        </>);
+        return !PresBox.Instance || !this.props.presPaths ? (null) : <>
+            <div key="presorder">{PresBox.Instance.order}</div>
+            <svg key="svg" className={presPaths}>
+                <defs>
+                    <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+                        <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" />
+                    </marker>
+                    <marker id="square" markerWidth="3" markerHeight="3" overflow="visible"
+                        refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+                        <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" />
+                    </marker>
+                    <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4"
+                        orient="auto" overflow="visible">
+                        <rect x="1" y="1" width="5" height="5" fill="#69a6db" />
+                    </marker>
+
+                    <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7"
+                        orient="auto" overflow="visible">
+                        <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" />
+                    </marker>
+                </defs>
+                {PresBox.Instance.paths}
+            </svg>
+        </>;
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0918e8389..3a9f31bef 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -130,6 +130,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 const tbox = Docs.Create.TextDocument("", {
                     _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
                     _fontFamily: StrCast(Doc.UserDoc().fontFamily),
+                    _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
                     title: "-typed text-"
                 });
                 const template = FormattedTextBox.DefaultLayout;
@@ -493,7 +494,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             d.page = -1;
             return d;
         });
-        const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+        const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
         const portal = Doc.MakeAlias(summary);
         Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
         Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
index 278f3b964..e3ced887d 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
@@ -303,7 +303,6 @@
                 margin-left: auto;
 
                 .permissions-select {
-                    z-index: 1;
                     border: none;
                     background-color: inherit;
                     width: 75px;
@@ -734,7 +733,7 @@
     background: #eeeeee;
     border-top: 1px solid;
     border-left: 1px solid;
-    
+
     &:hover {
         border: 0.75px solid rgb(122, 28, 28);
     }
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
index 50597f2eb..e128f6aab 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -184,7 +184,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             const noviceReqFields = ["author", "creationDate"];
             const noviceLayoutFields = ["curPage"];
             const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL") && key !== "UseCors")),
-            ...noviceReqFields, ...noviceLayoutFields]
+            ...noviceReqFields, ...noviceLayoutFields];
             for (const key of noviceKeys.sort()) {
                 const contents = this.selectedDoc[key];
                 if (key[0] === "#") {
@@ -200,7 +200,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                             <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
                         </div>);
                     } else {
-                        let contentElement = <EditableView key="editableView"
+                        const contentElement = <EditableView key="editableView"
                             contents={value}
                             height={13}
                             fontSize={10}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 70ebca5e7..4e279c659 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -304,7 +304,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
                 (e: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         undoBatch(action(() => {
-                            const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
+                            const text = Docs.Create.TextDocument("", { _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 150, _height: 50 });
                             FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
                             Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
                             this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 8ecde959f..519b78add 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -93,7 +93,6 @@ export class LinkMenu extends React.Component<Props> {
     }
 
     render() {
-        console.log("computed", this.position.x, this.position.b);
         const sourceDoc = this.props.docView.props.Document;
         const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
         return <div className="linkMenu" ref={this._linkMenuRef} >
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 5c8cb5e35..7b9a32dbe 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -109,12 +109,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         this._linkPlayDisposer?.();
         this._scrubbingDisposer?.();
     }
+
+    @action
     componentDidMount() {
         if (!this.dataDoc.markerAmount) {
             this.dataDoc.markerAmount = 0;
         }
 
-        runInAction(() => this.audioState = this.path ? "paused" : undefined);
+        this.audioState = this.path ? "paused" : undefined;
         this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID,
             scrollLinkId => {
                 if (scrollLinkId) {
@@ -285,7 +287,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     // creates a text document for dictation
     onFile = (e: any) => {
         const newDoc = Docs.Create.TextDocument("", {
-            title: "", _chromeStatus: "disabled",
+            _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: "", _chromeStatus: "disabled",
             x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
             _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height)
         });
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index e6b8928d4..2dd3bba91 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -133,6 +133,8 @@
             bottom: 0;
             width: 100%;
             transform-origin: bottom left;
+            opacity: 0.1;
+            transition: opacity 0.5s;
         }
     }
 
@@ -144,4 +146,9 @@
             display:inline-block;
         }
     }
+    > .documentView-styleWrapper {
+        > .documentView-captionWrapper {
+            opacity: 1;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bf42f30fe..db6d30aac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -771,7 +771,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
             }
             moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
-            Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" });
+            Doc.AreProtosEqual(this.props.Document, Cast(Doc.UserDoc()["sidebar-userDoc"], Doc, null)) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" });
         }
 
         const collectionAcl = GetEffectiveAcl(this.props.ContainingCollectionDoc?.[DataSym]);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dd3914df7..5f31f8c8d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -111,23 +111,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         }).then(function (stream) {
             gumStream = stream;
             recorder = new MediaRecorder(stream);
-            recorder.ondataavailable = async function (e: any) {
-                const formData = new FormData();
-                formData.append("file", e.data);
-                const res = await fetch(Utils.prepend("/uploadFormData"), {
-                    method: 'POST',
-                    body: formData
-                });
-                const files = await res.json();
-                const url = Utils.prepend(files[0].path);
-                // upload to server with known URL
-                const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 });
-                audioDoc.treeViewExpandedView = "layout";
-                const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc));
-                if (audioAnnos === undefined) {
-                    this.dataDoc[this.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
-                } else {
-                    audioAnnos.push(audioDoc);
+            recorder.ondataavailable = async (e: any) => {
+                const [{ result }] = await Networking.UploadFilesToServer(e.data);
+                if (!(result instanceof Error)) {
+                    const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 });
+                    audioDoc.treeViewExpandedView = "layout";
+                    const audioAnnos = Cast(self.dataDoc[self.fieldKey + "-audioAnnotations"], listSpec(Doc));
+                    if (audioAnnos === undefined) {
+                        self.dataDoc[self.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
+                    } else {
+                        audioAnnos.push(audioDoc);
+                    }
                 }
             };
             runInAction(() => self._audioState = 2);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index d30ea03b1..ba59c2df7 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1459,7 +1459,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>);
             }
             tags.push(
-                <div className="progressivizeButton" onPointerLeave={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
+                <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
                     <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div>
                     <div className="progressivizeButton-frame">{doc.appearFrame}</div>
                     <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index afdd8fea2..160f4ba72 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -32,8 +32,8 @@
     .formattedTextBox-dictation {
         height: 12px;
         width: 10px;
-        top: 0px;
-        left: 0px;
+        bottom: 5px;
+        right: 8px;
         position: absolute;
     }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 7a6d263f9..b473ad425 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -100,7 +100,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _pause: boolean = false;
 
     @computed get _recording() { return this.dataDoc.audioState === "recording"; }
-    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
+    set _recording(value) {
+        this.dataDoc.audioState = value ? "recording" : undefined;
+    }
 
     @observable private _entered = false;
 
@@ -350,7 +352,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     updateTitle = () => {
         if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"]) {
+            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
+            Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey) {
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
             const str = node.textContent;
@@ -383,7 +386,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let tr = this._editorView.state.tr;
             const flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
-            console.log("Search:" + this.rootDoc.title + " " + this._searchIndex + " => " + (this._searchIndex + 1 > flattened.length - 1 ? 0 : this._searchIndex + 1));
             this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
             if (backward === true) {
                 if (this._searchIndex > 1) {
@@ -658,7 +660,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0];
             mainBulletList.push.apply(mainBulletList, newBullets);
         }
-        console.log(mainBulletList.length);
         const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 });
         mainBulletList.push(title);
         const doc = Docs.Create.FreeformDocument(mainBulletList, {
@@ -713,7 +714,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     recordDictation = () => {
         DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
+            interimHandler: this.setDictationContent,
             continuous: { indefinite: false },
         }).then(results => {
             if (results && [DictationManager.Controls.Infringed].includes(results)) {
@@ -724,22 +725,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
 
-    recordBullet = async () => {
-        const completedCue = "end session";
-        const results = await DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-            terminators: [completedCue, "bullet", "next"]
-        });
-        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
-            DictationManager.Controls.stop();
-            return;
-        }
-        this.nextBullet(this._editorView!.state.selection.to);
-        setTimeout(this.recordBullet, 2000);
-    }
-
-    setCurrentBulletContent = (value: string) => {
+    setDictationContent = (value: string) => {
         if (this._editorView) {
             const state = this._editorView.state;
             const now = Date.now();
@@ -754,33 +740,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     }
                 }
             }
-            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
-            this._break = false;
-            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
             const from = state.selection.from;
-            const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
-            this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
-        }
-    }
-
-    nextBullet = (pos: number) => {
-        if (this._editorView) {
-            const frag = Fragment.fromArray(this.newListItems(2));
-            if (this._editorView.state.doc.resolve(pos).depth >= 2) {
-                const slice = new Slice(frag, 2, 2);
-                let state = this._editorView.state;
-                this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
-                pos += 4;
-                state = this._editorView.state;
-                this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+            this._break = false;
+            if (this.props.Document.recordingStart) {
+                const recordingStart = DateCast(this.props.Document.recordingStart)?.date.getTime();
+                value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
             }
+            const tr = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
+            this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, from + value.length + 1)));
         }
     }
 
-    private newListItems = (count: number) => {
-        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
-    }
-
     _keymap: any = undefined;
     _rules: RichTextRules | undefined;
     @computed get config() {
@@ -903,21 +873,23 @@ 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();
-            },
+            search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
             { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
 
-        this._disposers.record = reaction(() => this._recording,
-            () => {
-                if (this._recording) {
-                    setTimeout(action(() => {
-                        this.stopDictation(true);
-                        setTimeout(() => this.recordDictation(), 500);
-                    }), 500);
-                } else setTimeout(() => this.stopDictation(true), 0);
-            }
-        );
+        this._disposers.selected = reaction(() => this.props.isSelected(), action(() => this._recording = false));
+
+        if (!this.props.dontRegisterView) {
+            this._disposers.record = reaction(() => this._recording,
+                () => {
+                    if (this._recording) {
+                        setTimeout(action(() => {
+                            this.stopDictation(true);
+                            setTimeout(() => this.recordDictation(), 500);
+                        }), 500);
+                    } else setTimeout(() => this.stopDictation(true), 0);
+                }
+            );
+        }
         this._disposers.scrollToRegion = reaction(
             () => StrCast(this.layoutDoc.scrollToLinkID),
             async (scrollToLinkID) => {
@@ -1410,7 +1382,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const self = this;
         return new Plugin({
             view(newView) {
-                self.props.isSelected(true) && (RichTextMenu.Instance.view = newView);
+                self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
                 return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props });
             }
         });
@@ -1432,7 +1404,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
-        //DictationManager.Controls.stop(false);
         this.endUndoTypingBatch();
         this.doLinkOnDeselect();
 
@@ -1545,13 +1516,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         width: "100%",
                         height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
                         background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
-                        opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
                         pointerEvents: interactive ? undefined : "none",
                         fontSize: Cast(this.layoutDoc._fontSize, "string", null),
                         fontWeight: Cast(this.layoutDoc._fontWeight, "number", null),
                         fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
-                        transition: "opacity 1s"
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyPress}
@@ -1615,14 +1584,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} />
                         </div>}
                     {!this.layoutDoc._showAudio ? (null) :
-                        <div className="formattedTextBox-dictation"
-                            onPointerDown={e => {
-                                runInAction(() => this._recording = !this._recording);
-                                setTimeout(() => this._editorView!.focus(), 500);
-                                e.stopPropagation();
-                            }} >
+                        <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
                             <FontAwesomeIcon className="formattedTextBox-audioFont"
-                                style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+                                style={{
+                                    color: this._recording ? "red" : "blue",
+                                    transitionDelay: "0.6s",
+                                    opacity: this._recording ? 1 : 0.25,
+                                }}
+                                icon={"microphone"} size="sm" />
                         </div>}
                 </div>
             </div>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index e93fc86b5..bed6c83b4 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -442,7 +442,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight();
-    selectElement = (doc: Doc) => { /* this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); */ }
+    selectElement = (doc: Doc) => { /* this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); */ };
     returnHeight = () => 31 + 31 * 6;
     returnLength = () => Math.min(window.innerWidth, 51 + 205 * Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length);
 
-- 
cgit v1.2.3-70-g09d2


From a9e08e0504e8002bc5d991b6a13777577ddd8f9f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Aug 2020 20:00:29 -0400
Subject: fixed dockingview tab highlighting.  fixed text hyperlink link
 following to show target correctly (not clipped)  and avoid exceptions.

---
 src/client/util/DocumentManager.ts                        |  1 -
 src/client/views/collections/CollectionDockingView.tsx    | 11 +++++------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  5 +++--
 .../views/nodes/formattedText/FormattedTextBoxComment.tsx | 15 +++++++++++----
 4 files changed, 19 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 962294933..bd57e7f48 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -231,7 +231,6 @@ export class DocumentManager {
                     containerDoc.currentTimecode = targetTimecode;
                     const targetContext = await target?.context as Doc;
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    console.log(targetNavContext);
                     DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
                 } else {
                     finished?.();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6277fbb14..27e613c03 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -775,21 +775,20 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     componentDidMount() {
+        const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
+        const selected = () => SelectionManager.SelectedDocuments().some(v => Doc.AreProtosEqual(v.props.Document, this._document));
+        const updateTabColor = () => this._tab && (this._tab.style.backgroundColor = selected() ? color() : "");
         const observer = new _global.ResizeObserver(action((entries: any) => {
             for (const entry of entries) {
                 this._panelWidth = entry.contentRect.width;
                 this._panelHeight = entry.contentRect.height;
             }
+            updateTabColor();
         }));
         observer.observe(this.props.glContainer._element[0]);
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged();
-        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white") }),
-            (data) => {
-                const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document));
-                this._tab && (this._tab.style.backgroundColor = selected ? data.color : "");
-            }
-        );
+        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), () => updateTabColor(), { fireImmediately: true });
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b473ad425..77483a179 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -895,14 +895,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             async (scrollToLinkID) => {
                 const findLinkFrag = (frag: Fragment, editor: EditorView) => {
                     const nodes: Node[] = [];
+                    let offset = 0;
                     frag.forEach((node, index) => {
                         const examinedNode = findLinkNode(node, editor);
                         if (examinedNode?.textContent) {
                             nodes.push(examinedNode);
-                            start += index;
+                            offset = index;
                         }
                     });
-                    return { frag: Fragment.fromArray(nodes), start: start };
+                    return { frag: Fragment.fromArray(nodes), start: start + offset };
                 };
                 const findLinkNode = (node: Node, editor: EditorView) => {
                     if (!node.isText) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 5da0337b8..55d225adc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -232,6 +232,16 @@ export class FormattedTextBoxComment {
             const mark = child ? findLinkMark(child.marks) : undefined;
             const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl;
             if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
+                try {
+                    ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+                } catch (e) { }
+                FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
+                FormattedTextBoxComment.tooltipText = document.createElement("div");
+                FormattedTextBoxComment.tooltipText.style.width = "100%";
+                FormattedTextBoxComment.tooltipText.style.height = "100%";
+                FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
+                FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+
                 FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
                 (FormattedTextBoxComment.tooltipText as any).href = href;
                 if (href.startsWith("https://en.wikipedia.org/wiki/")) {
@@ -241,12 +251,9 @@ export class FormattedTextBoxComment {
                     FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
                 }
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     FormattedTextBoxComment.tooltipText.textContent = "target not found...";
                     (FormattedTextBoxComment.tooltipText as any).href = "";
-                    const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    try {
-                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-                    } catch (e) { }
                     docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
                             (FormattedTextBoxComment.tooltipText as any).href = href;
-- 
cgit v1.2.3-70-g09d2


From 505ad0d2e2a37795f1877b2319a8ba3a1ce65d28 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 24 Aug 2020 11:21:20 -0400
Subject: cleaned up all library() calls to add icons.  fixed import styles. 
 fixed keyboard events for presentation views to trigger when no input box is
 the target.

---
 src/client/DocServer.ts                            |   2 +-
 src/client/apis/GoogleAuthenticationManager.tsx    |   8 +-
 .../apis/google_docs/GooglePhotosClientUtils.ts    |   2 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/GroupManager.tsx                   |  43 +++----
 src/client/util/GroupMemberView.tsx                |  22 ++--
 .../util/Import & Export/DirectoryImportBox.tsx    |   9 +-
 .../util/Import & Export/ImportMetadataEntry.tsx   |   8 +-
 src/client/util/SettingsManager.tsx                |  30 +++--
 src/client/util/SharingManager.tsx                 |  53 ++++----
 src/client/views/AntimodeMenu.tsx                  |   2 +-
 src/client/views/ContextMenu.tsx                   |   5 -
 src/client/views/ContextMenuItem.tsx               |   7 +-
 src/client/views/DictationOverlay.tsx              |   2 +-
 src/client/views/DocumentButtonBar.tsx             |  32 ++---
 src/client/views/DocumentDecorations.tsx           |  43 ++-----
 src/client/views/EditableView.tsx                  |  10 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/GlobalKeyHandler.ts               |  14 +--
 src/client/views/InkingStroke.tsx                  |  17 +--
 src/client/views/MainView.tsx                      |  49 ++++----
 src/client/views/MainViewModal.tsx                 |   2 +-
 src/client/views/PropertiesButtons.tsx             |  45 +++----
 src/client/views/ScriptingRepl.tsx                 |  17 +--
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionMenu.tsx    |   8 +-
 .../views/collections/CollectionSchemaCells.tsx    |  58 ++++-----
 .../CollectionSchemaMovableTableHOC.tsx            |   6 +-
 .../views/collections/CollectionSchemaView.tsx     |  15 +--
 .../CollectionStackingViewFieldColumn.tsx          |  14 +--
 src/client/views/collections/CollectionView.tsx    |   7 +-
 .../views/collections/ParentDocumentSelector.tsx   |  24 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  22 ++--
 .../collectionFreeForm/FormatShapePane.tsx         |   4 +-
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   9 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  11 +-
 .../collectionFreeForm/PropertiesView.tsx          |  52 ++++----
 src/client/views/linking/LinkEditor.tsx            |   5 -
 src/client/views/linking/LinkMenu.tsx              |  16 +--
 src/client/views/linking/LinkMenuItem.tsx          |  26 ++--
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FaceRectangles.tsx          |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  15 +--
 src/client/views/nodes/PresBox.tsx                 |  33 +++--
 src/client/views/nodes/RadialMenuItem.tsx          |   8 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   6 +-
 src/client/views/nodes/SliderBox.tsx               |   5 -
 src/client/views/nodes/WebBox.tsx                  |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  13 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  19 ++-
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/pdf/Annotation.tsx                |   4 +-
 src/client/views/pdf/PDFMenu.tsx                   |  14 +--
 src/client/views/pdf/PDFViewer.tsx                 |  18 +--
 src/client/views/search/IconBar.tsx                |  25 +---
 src/client/views/search/IconButton.tsx             | 140 ++++++---------------
 src/client/views/webcam/DashWebRTCVideo.tsx        |  24 ++--
 src/mobile/AudioUpload.tsx                         |   4 +-
 src/mobile/ImageUpload.tsx                         |  20 +--
 src/mobile/MobileInterface.tsx                     |  43 +++----
 61 files changed, 423 insertions(+), 685 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index dde75497c..9da5b8632 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,7 @@ import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../fields/RefField';
 import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
-import GestureOverlay from './views/GestureOverlay';
+import { GestureOverlay } from './views/GestureOverlay';
 import MobileInkOverlay from '../mobile/MobileInkOverlay';
 import { runInAction } from 'mobx';
 import { ObjectField } from '../fields/ObjectField';
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 117d1fa1e..cda108058 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,17 +1,17 @@
-import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
 import { Opt } from "../../fields/Doc";
 import { Networking } from "../Network";
-import "./GoogleAuthenticationManager.scss";
 import { Scripting } from "../util/Scripting";
+import { MainViewModal } from "../views/MainViewModal";
+import "./GoogleAuthenticationManager.scss";
 
 const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
 const prompt = "Paste authorization code here...";
 
 @observer
-export default class GoogleAuthenticationManager extends React.Component<{}> {
+export class GoogleAuthenticationManager extends React.Component<{}> {
     public static Instance: GoogleAuthenticationManager;
     private authenticationLink: Opt<string> = undefined;
     @observable private openState = false;
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 92eaf2e73..899e65a16 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -11,7 +11,7 @@ import { Utils } from "../../../Utils";
 import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
 import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox";
-import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
+import { GoogleAuthenticationManager } from "../GoogleAuthenticationManager";
 import Photos = require('googlephotos');
 
 export namespace GooglePhotos {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3ee0c7003..be584c3cf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -908,7 +908,7 @@ export class CurrentUserUtils {
     // Import sidebar is where shared documents are contained
     static setupImportSidebar(doc: Doc) {
         if (doc["sidebar-import-documents"] === undefined) {
-            doc["sidebar-import-documents"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Imported Documents", forceActive: true, _showTitle: "title", childDropAction: "alias", _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled", system: true, _height: 500 }));
+            doc["sidebar-import-documents"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Imported Documents", forceActive: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled", system: true }));
         }
         if (doc["sidebar-import"] === undefined) {
             const uploads = Cast(doc["sidebar-import-documents"], Doc, null);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index d03989675..612ca7be7 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,24 +1,19 @@
-import * as React from "react";
-import { observable, action, runInAction, computed } from "mobx";
-import { SelectionManager } from "./SelectionManager";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { library } from "@fortawesome/fontawesome-svg-core";
-import SharingManager, { User } from "./SharingManager";
-import { Utils } from "../../Utils";
-import * as RequestPromise from "request-promise";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
 import Select from 'react-select';
-import "./GroupManager.scss";
-import { StrCast, Cast } from "../../fields/Types";
-import GroupMemberView from "./GroupMemberView";
+import * as RequestPromise from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { Cast, StrCast } from "../../fields/Types";
 import { setGroups } from "../../fields/util";
+import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
+import { MainViewModal } from "../views/MainViewModal";
 import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-
-library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
+import "./GroupManager.scss";
+import { GroupMemberView } from "./GroupMemberView";
+import { SharingManager, User } from "./SharingManager";
 
 /**
  * Interface for options for the react-select component
@@ -29,7 +24,7 @@ export interface UserOptions {
 }
 
 @observer
-export default class GroupManager extends React.Component<{}> {
+export class GroupManager extends React.Component<{}> {
 
     static Instance: GroupManager;
     @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@@ -314,7 +309,7 @@ export default class GroupManager extends React.Component<{}> {
                     <div className={"close-button"} onClick={action(() => {
                         this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false;
                     })}>
-                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                        <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
                     </div>
                 </div>
                 <input
@@ -398,19 +393,19 @@ export default class GroupManager extends React.Component<{}> {
                 <div className="group-heading">
                     <p><b>Manage Groups</b></p>
                     <button onClick={action(() => this.createGroupModalOpen = true)}>
-                        <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group
+                        <FontAwesomeIcon icon={"plus-hexagon"} size={"sm"} /> Create Group
                     </button>
                     <div className={"close-button"} onClick={this.close}>
-                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                        <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
                     </div>
                 </div>
                 <div className="main-container">
                     <div
                         className="sort-groups"
                         onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
-                        Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
-                            : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
-                                : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />
+                        Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+                            : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+                                : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />
                         }
                     </div>
                     <div className="group-body">
@@ -421,7 +416,7 @@ export default class GroupManager extends React.Component<{}> {
                             >
                                 <div className="group-name" >{group.groupName}</div>
                                 <div className="group-info" onClick={action(() => this.currentGroup = group)}>
-                                    <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+                                    <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
                                 </div>
                             </div>
                         )}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 531ef988a..4ead01e9f 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,25 +1,21 @@
-import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import GroupManager, { UserOptions } from "./GroupManager";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { StrCast } from "../../fields/Types";
-import { action, observable } from "mobx";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as fa from '@fortawesome/free-solid-svg-icons';
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
 import Select from "react-select";
 import { Doc } from "../../fields/Doc";
+import { StrCast } from "../../fields/Types";
+import { MainViewModal } from "../views/MainViewModal";
+import { GroupManager, UserOptions } from "./GroupManager";
 import "./GroupMemberView.scss";
 
-library.add(fa.faTimes, fa.faTrashAlt);
-
 interface GroupMemberViewProps {
     group: Doc;
     onCloseButtonClick: () => void;
 }
 
 @observer
-export default class GroupMemberView extends React.Component<GroupMemberViewProps> {
+export class GroupMemberView extends React.Component<GroupMemberViewProps> {
 
     @observable private memberSort: "ascending" | "descending" | "none" = "none";
 
@@ -43,7 +39,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                     >
                     </input>
                     <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
-                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                        <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
                     </div>
                     {GroupManager.Instance.hasEditAccess(this.props.group) ?
                         <div className="group-buttons">
@@ -88,7 +84,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                             </div>
                             {hasEditAccess ?
                                 <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
-                                    <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} />
+                                    <FontAwesomeIcon icon={"trash-alt"} size={"sm"} />
                                 </div>
                                 : null}
                         </div>
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 77f13e9f4..d04270afa 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCloudUploadAlt, faPlus, faTag } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { BatchedArray } from "array-batcher";
 import "fs";
@@ -47,7 +45,6 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
 
     constructor(props: FieldViewProps) {
         super(props);
-        library.add(faTag, faPlus);
         const doc = this.props.Document;
         this.editingMetadata = this.editingMetadata || false;
         this.persistent = this.persistent || false;
@@ -301,7 +298,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
                                 opacity: uploading ? 0 : 1,
                                 transition: "0.4s opacity ease"
                             }}>
-                                <FontAwesomeIcon icon={faCloudUploadAlt} color="#FFFFFF" size={"2x"} />
+                                <FontAwesomeIcon icon={"cloud-upload-alt"} color="#FFFFFF" size={"2x"} />
                             </div>
                             <img
                                 style={{
@@ -366,7 +363,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
                                 opacity: uploading ? 0 : 1,
                                 transition: "0.4s opacity ease"
                             }}
-                            icon={isEditing ? faCloudUploadAlt : faTag}
+                            icon={isEditing ? "cloud-upload-alt" : "tag"}
                             color="#FFFFFF"
                             size={"1x"}
                         />
@@ -399,7 +396,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
                                         marginLeft: 6.4,
                                         marginTop: 5.2
                                     }}
-                                    icon={faPlus}
+                                    icon={"plus"}
                                     size={"1x"}
                                 />
                             </div>
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index dcb94e2e0..1870213b9 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx	
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -4,7 +4,6 @@ import { EditableView } from "../../views/EditableView";
 import { action, computed } from "mobx";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faPlus } from "@fortawesome/free-solid-svg-icons";
-import { library } from '@fortawesome/fontawesome-svg-core';
 import { Doc } from "../../../fields/Doc";
 import { StrCast, BoolCast } from "../../../fields/Types";
 
@@ -24,11 +23,6 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
     private valueRef = React.createRef<EditableView>();
     private checkRef = React.createRef<HTMLInputElement>();
 
-    constructor(props: KeyValueProps) {
-        super(props);
-        library.add(faPlus);
-    }
-
     @computed
     public get valid() {
         return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
@@ -132,7 +126,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
                 </div>
                 <div onClick={() => this.props.remove(this)} title={"Delete Entry"}>
                     <FontAwesomeIcon
-                        icon={faPlus}
+                        icon={"plus"}
                         color={"red"}
                         size={"1x"}
                         style={{
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index bc5e1e9d3..e9befc465 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,27 +1,25 @@
-import { observable, runInAction, action, computed } from "mobx";
-import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { SelectionManager } from "./SelectionManager";
-import "./SettingsManager.scss";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Networking } from "../Network";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from "../../Utils";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { ColorState, SketchPicker } from "react-color";
 import { Doc } from "../../fields/Doc";
-import GroupManager from "./GroupManager";
-import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { BoolCast, StrCast } from "../../fields/Types";
+import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
+import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
 import { DocServer } from "../DocServer";
-import { BoolCast, StrCast, NumCast } from "../../fields/Types";
+import { Networking } from "../Network";
+import { MainViewModal } from "../views/MainViewModal";
+import { CurrentUserUtils } from "./CurrentUserUtils";
+import { GroupManager } from "./GroupManager";
+import "./SettingsManager.scss";
 import { undoBatch } from "./UndoManager";
-import { ColorState, SketchPicker } from "react-color";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
 @observer
-export default class SettingsManager extends React.Component<{}> {
+export class SettingsManager extends React.Component<{}> {
     public static Instance: SettingsManager;
     static _settingsStyle = addStyleSheet();
     @observable private isOpen = false;
@@ -166,7 +164,7 @@ export default class SettingsManager extends React.Component<{}> {
                     {CurrentUserUtils.GuestDashboard ? "Exit" : "Log Out"}
                 </button>
                 <div className="close-button" onClick={this.close}>
-                    <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} />
+                    <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
                 </div>
             </div>
             <div className="settings-content">
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index b9918e900..a73cb63d0 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,29 +1,24 @@
-import { observable, runInAction, action } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
 import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, AclAdmin, AclPrivate, DocListCast, DataSym } from "../../fields/Doc";
-import { DocServer } from "../DocServer";
-import { Cast, StrCast } from "../../fields/Types";
+import Select from "react-select";
 import * as RequestPromise from "request-promise";
+import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { Cast, StrCast } from "../../fields/Types";
+import { distributeAcls, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
 import { Utils } from "../../Utils";
-import "./SharingManager.scss";
-import { observer } from "mobx-react";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { DocumentView } from "../views/nodes/DocumentView";
-import { SelectionManager } from "./SelectionManager";
-import { DocumentManager } from "./DocumentManager";
+import { DocServer } from "../DocServer";
 import { CollectionView } from "../views/collections/CollectionView";
 import { DictationOverlay } from "../views/DictationOverlay";
-import GroupManager, { UserOptions } from "./GroupManager";
-import GroupMemberView from "./GroupMemberView";
-import Select from "react-select";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { List } from "../../fields/List";
-import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util";
+import { MainViewModal } from "../views/MainViewModal";
+import { DocumentView } from "../views/nodes/DocumentView";
 import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { library } from "@fortawesome/fontawesome-svg-core";
-
-library.add(fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
+import { DocumentManager } from "./DocumentManager";
+import { GroupManager, UserOptions } from "./GroupManager";
+import { GroupMemberView } from "./GroupMemberView";
+import "./SharingManager.scss";
 
 export interface User {
     email: string;
@@ -58,7 +53,7 @@ interface ValidatedUser {
 
 
 @observer
-export default class SharingManager extends React.Component<{}> {
+export class SharingManager extends React.Component<{}> {
     public static Instance: SharingManager;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
     @observable private users: ValidatedUser[] = []; // the list of users with notificationDocs
@@ -486,7 +481,7 @@ export default class SharingManager extends React.Component<{}> {
                 >
                     <div className={"padding"}>{group.groupName}</div>
                     <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
-                        <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+                        <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
                     </div>
                     <div className="edit-actions">
                         <select
@@ -522,7 +517,7 @@ export default class SharingManager extends React.Component<{}> {
                             style={{ backgroundColor: this.copied ? "lawngreen" : "gainsboro" }}
                             onClick={this.copy}
                         >
-                            <FontAwesomeIcon icon={fa.faCopy} />
+                            <FontAwesomeIcon icon={"copy"} />
                         </div>
                     </div>
                 }
@@ -584,9 +579,9 @@ export default class SharingManager extends React.Component<{}> {
                             <div
                                 className="user-sort"
                                 onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
-                                Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
-                                    : this.individualSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
-                                        : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />}
+                                Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+                                    : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+                                        : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
                             </div>
                             <div className={"users-list"} style={{ display: "block" }}>{/*200*/}
                                 {userListContents}
@@ -596,9 +591,9 @@ export default class SharingManager extends React.Component<{}> {
                             <div
                                 className="user-sort"
                                 onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
-                                Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
-                                    : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
-                                        : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />}
+                                Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+                                    : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+                                        : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
 
                             </div>
                             <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/}
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index ccc54058d..32fb24b32 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -8,7 +8,7 @@ export interface AntimodeMenuProps {
  * This is an abstract class that serves as the base for a PDF-style or Marquee-style
  * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
  */
-export default abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Component<T, {}> {
+export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Component<T, {}> {
     protected _offsetY: number = 0;
     protected _offsetX: number = 0;
     protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 07f7b8e6d..cb34b8873 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -3,14 +3,9 @@ import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextM
 import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import "./ContextMenu.scss";
-import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
 import Measure from "react-measure";
 
-library.add(faSearch);
-library.add(faCircle);
-
 @observer
 export class ContextMenu extends React.Component {
     static Instance: ContextMenu;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 7e233ec04..22bfbe217 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,13 +1,10 @@
 import React = require("react");
 import { observable, action } from "mobx";
 import { observer } from "mobx-react";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { UndoManager } from "../util/UndoManager";
 
-library.add(faAngleRight);
-
 export interface OriginalMenuProps {
     description: string;
     event: (stuff?: any) => void;
@@ -115,7 +112,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
                         style={{ alignItems: "center" }} >
                         {this.props.description}
-                        <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} />
+                        <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
                     </div>
                     {submenu}
                 </div>
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 9ed14509f..f4f96da8a 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -4,7 +4,7 @@ import "normalize.css";
 import * as React from 'react';
 import { DictationManager } from '../util/DictationManager';
 import "./Main.scss";
-import MainViewModal from './MainViewModal';
+import { MainViewModal } from './MainViewModal';
 
 @observer
 export class DictationOverlay extends React.Component {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 17ed211dd..659adebbc 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,48 +1,34 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc } from "../../fields/Doc";
 import { RichTextField } from '../../fields/RichTextField';
 import { Cast, NumCast } from "../../fields/Types";
 import { emptyFunction, setupMoveUpEvents } from "../../Utils";
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DragManager } from '../util/DragManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { SharingManager } from '../util/SharingManager';
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
 import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { Tooltip } from '@material-ui/core';
-import SharingManager from '../util/SharingManager';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { SelectionManager } from '../util/SelectionManager';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faArrowAltCircleRight);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faPhotoVideo);
-
 const cloud: IconProp = "cloud-upload-alt";
 const fetch: IconProp = "sync-alt";
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8e44bcf8f..7b665ccd3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,4 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleDoubleLeft, faAngleDoubleRight, faAngleLeft, faAngleRight, faArrowAltCircleDown, faArrowAltCircleUp, faCaretUp, faCheckCircle, faCloudUploadAlt, faExternalLinkAlt, faFilePdf, faFilm, faImage, faLink, faObjectGroup, faPause, faShare, faStickyNote, faStopCircle, faSyncAlt, faTag, faTextHeight, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, reaction, runInAction } from "mobx";
@@ -19,35 +18,13 @@ import { SelectionManager } from "../util/SelectionManager";
 import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import FormatShapePane from './collections/collectionFreeForm/FormatShapePane';
+import { FormatShapePane } from './collections/collectionFreeForm/FormatShapePane';
 import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
 import { DocumentView } from "./nodes/DocumentView";
 import React = require("react");
 import e = require('express');
 
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm, faTextHeight);
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faAngleDoubleLeft);
-library.add(faAngleDoubleRight);
-library.add(faAngleLeft);
-library.add(faAngleRight);
-library.add(faPause);
-library.add(faExternalLinkAlt);
-
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
     static Instance: DocumentDecorations;
@@ -587,12 +564,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
     }
     public static DocumentIcon(layout: string) {
-        const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
-            layout.indexOf("ImageBox") !== -1 ? faImage :
-                layout.indexOf("Formatted") !== -1 ? faStickyNote :
-                    layout.indexOf("Video") !== -1 ? faFilm :
-                        layout.indexOf("Collection") !== -1 ? faObjectGroup :
-                            faCaretUp;
+        const button = layout.indexOf("PDFBox") !== -1 ? "file-pdf" :
+            layout.indexOf("ImageBox") !== -1 ? "image" :
+                layout.indexOf("Formatted") !== -1 ? "sticky-note" :
+                    layout.indexOf("Video") !== -1 ? "film" :
+                        layout.indexOf("Collection") !== -1 ? "object-group" :
+                            "caret-up";
         return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
     }
     render() {
@@ -615,7 +592,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     <Tooltip title={<><div className="dash-tooltip">Close</div></>} placement="top">
                         <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
                             {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
-                            <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
+                            <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
                         </div></Tooltip>) : (null);
 
         const titleArea = this._edtingTitle ?
@@ -705,7 +682,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
                             <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
                                 onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
-                                <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+                                <FontAwesomeIcon className="documentdecorations-times" icon={"arrow-alt-circle-up"} size="lg" />
                             </div></Tooltip>}
                     <div id={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
                         onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index a4b059482..d0e6ed463 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -4,11 +4,8 @@ import { observer } from 'mobx-react';
 import * as Autosuggest from 'react-autosuggest';
 import { ObjectField } from '../../fields/ObjectField';
 import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
-import "./EditableView.scss";
 import { DragManager } from '../util/DragManager';
-import { ComputedField } from '../../fields/ScriptField';
-import { FieldValue } from '../../fields/Types';
-import { PresBox } from './nodes/PresBox';
+import "./EditableView.scss";
 
 export interface EditableProps {
     /**
@@ -172,6 +169,7 @@ export class EditableView extends React.Component<EditableProps> {
                     onPointerDown: this.stopPropagation,
                     onClick: this.stopPropagation,
                     onPointerUp: this.stopPropagation,
+                    onKeyPress: this.stopPropagation,
                     value: this.props.autosuggestProps.value,
                     onChange: this.props.autosuggestProps.onChange
                 }}
@@ -180,6 +178,10 @@ export class EditableView extends React.Component<EditableProps> {
                 defaultValue={this.props.GetValue()}
                 onKeyDown={this.onKeyDown}
                 autoFocus={true}
+                onKeyPress={e => {
+                    e.stopPropagation();
+                    (e.nativeEvent as any).Aaaa = 3;
+                }}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)}
                 onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
                 style={{ display: this.props.display, fontSize: this.props.fontSize, minWidth: 20 }}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 260cffc90..63711a3cb 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -26,7 +26,7 @@ import * as fitCurve from 'fit-curve';
 import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 
 @observer
-export default class GestureOverlay extends Touchable {
+export class GestureOverlay extends Touchable {
     static Instance: GestureOverlay;
 
     @observable public InkShape: string = "";
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 9e7f525b7..b29a04ea7 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -6,22 +6,22 @@ import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
 import { ScriptField } from "../../fields/ScriptField";
 import { Cast, PromiseValue } from "../../fields/Types";
-import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
 import { DocServer } from "../DocServer";
 import { DocumentType } from "../documents/DocumentTypes";
 import { DictationManager } from "../util/DictationManager";
 import { DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
-import SharingManager from "../util/SharingManager";
+import { SharingManager } from "../util/SharingManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CollectionDockingView } from "./collections/CollectionDockingView";
 import { DocumentDecorations } from "./DocumentDecorations";
 import { MainView } from "./MainView";
 import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
-import PDFMenu from "./pdf/PDFMenu";
+import { PDFMenu } from "./pdf/PDFMenu";
 import { ContextMenu } from "./ContextMenu";
-import GroupManager from "../util/GroupManager";
+import { GroupManager } from "../util/GroupManager";
 import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 
 const modifiers = ["control", "meta", "shift", "alt"];
@@ -31,7 +31,7 @@ type KeyControlInfo = {
     stopPropagation: boolean
 };
 
-export default class KeyManager {
+export class KeyManager {
     public static Instance: KeyManager = new KeyManager();
     private router = new Map<string, KeyHandler>();
 
@@ -121,9 +121,7 @@ export default class KeyManager {
                 }
 
                 const selected = SelectionManager.SelectedDocuments().slice();
-                UndoManager.RunInBatch(() => {
-                    selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
-                }, "delete");
+                UndoManager.RunInBatch(() => selected.map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
                 SelectionManager.DeselectAll();
                 break;
             case "arrowleft":
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 3376fcd97..b972d4e41 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,27 +1,22 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { action } 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 } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
+import { setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { InteractionUtils } from "../util/InteractionUtils";
+import { Scripting } from "../util/Scripting";
+import { UndoManager } from "../util/UndoManager";
+import { FormatShapePane } from "./collections/collectionFreeForm/FormatShapePane";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
-import { Scripting } from "../util/Scripting";
-import { Doc } from "../../fields/Doc";
-import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import { action } from "mobx";
-import { setupMoveUpEvents } from "../../Utils";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-
-
-library.add(faPaintBrush);
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3b10c0e6b..9d6bffa60 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,45 +8,49 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import Measure from 'react-measure';
+import * as rp from 'request-promise';
 import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
+import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, FieldValue, StrCast, NumCast } from '../../fields/Types';
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils, returnEmptyDoclist } from '../../Utils';
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
+import { Networking } from '../Network';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DocumentManager } from '../util/DocumentManager';
-import GroupManager from '../util/GroupManager';
+import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
+import { LinkManager } from '../util/LinkManager';
 import { Scripting } from '../util/Scripting';
+import { SearchUtil } from '../util/SearchUtil';
 import { SelectionManager } from '../util/SelectionManager';
-import SettingsManager from '../util/SettingsManager';
-import SharingManager from '../util/SharingManager';
+import { SettingsManager } from '../util/SettingsManager';
+import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import { FormatShapePane } from "./collections/collectionFreeForm/FormatShapePane";
+import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { PropertiesView } from './collections/collectionFreeForm/PropertiesView';
 import { CollectionLinearView } from './collections/CollectionLinearView';
-import CollectionMenu from './collections/CollectionMenu';
+import { CollectionMenu } from './collections/CollectionMenu';
 import { CollectionView, CollectionViewType } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
-import GestureOverlay from './GestureOverlay';
+import { GestureOverlay } from './GestureOverlay';
 import { ANTIMODEMENU_HEIGHT, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
-import KeyManager from './GlobalKeyHandler';
+import { KeyManager } from './GlobalKeyHandler';
 import { LinkMenu } from './linking/LinkMenu';
-import "./MainView.scss";
 import { AudioBox } from './nodes/AudioBox';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
@@ -57,14 +61,10 @@ import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { WebBox } from './nodes/WebBox';
 import { OverlayView } from './OverlayView';
-import PDFMenu from './pdf/PDFMenu';
+import { PDFMenu } from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { SearchBox } from './search/SearchBox';
-import { SearchUtil } from '../util/SearchUtil';
-import { Networking } from '../Network';
-import * as rp from 'request-promise';
-import { LinkManager } from '../util/LinkManager';
-import { PrefetchProxy } from '../../fields/Proxy';
+import "./MainView.scss";
 
 @observer
 export class MainView extends React.Component {
@@ -168,13 +168,13 @@ export class MainView extends React.Component {
 
         library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
             fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
-            fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
-            fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+            fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+            fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
             fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
-            fa.faSearch, fa.faFileDownload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+            fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
             fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faClipboard,
             fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
-            fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
+            fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
             fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote,
             fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
             fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
@@ -183,7 +183,10 @@ export class MainView extends React.Component {
             fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle,
             fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
             fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
-            fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faClosedCaptioning);
+            fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
+            fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
+            fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload,
+            fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 19387f619..34e0ff126 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -14,7 +14,7 @@ export interface MainViewOverlayProps {
 }
 
 @observer
-export default class MainViewModal extends React.Component<MainViewOverlayProps> {
+export class MainViewModal extends React.Component<MainViewOverlayProps> {
 
     render() {
         const p = this.props;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 9839222ec..4abb2cb7a 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,52 +1,39 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DataSym, AclEdit, AclAdmin } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc } from "../../fields/Doc";
+import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast, BoolCast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, Utils } from "../../Utils";
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { Cast, NumCast } from "../../fields/Types";
+import { ImageField } from '../../fields/URLField';
+import { GetEffectiveAcl } from '../../fields/util';
+import { emptyFunction, setupMoveUpEvents } from "../../Utils";
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { SharingManager } from '../util/SharingManager';
+import { undoBatch } from '../util/UndoManager';
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import './collections/ParentDocumentSelector.scss';
-import './PropertiesButtons.scss';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentView } from './nodes/DocumentView';
 import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
+import { PresBox } from './nodes/PresBox';
+import './PropertiesButtons.scss';
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { Tooltip } from '@material-ui/core';
-import { SelectionManager } from '../util/SelectionManager';
-import SharingManager from '../util/SharingManager';
-import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils';
-import { ImageField } from '../../fields/URLField';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { DocumentType } from '../documents/DocumentTypes';
-import { InkField } from '../../fields/InkField';
-import { PresBox } from './nodes/PresBox';
-import { GetEffectiveAcl } from '../../fields/util';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faArrowAltCircleRight);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faPhotoVideo);
 
 const cloud: IconProp = "cloud-upload-alt";
 const fetch: IconProp = "sync-alt";
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index db087fb23..c598b2861 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,17 +1,12 @@
-import * as React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import './ScriptingRepl.scss';
-import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
+import * as React from 'react';
 import { DocumentManager } from '../util/DocumentManager';
-import { OverlayView } from './OverlayView';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+import { CompileScript, Scripting, Transformer, ts } from '../util/Scripting';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
-
-library.add(faCaretDown);
-library.add(faCaretRight);
+import { OverlayView } from './OverlayView';
+import './ScriptingRepl.scss';
 
 @observer
 export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void, value: { [key: string]: any }, name?: string }> {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f1959460c..cc2220f62 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -791,7 +791,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         observer.observe(this.props.glContainer._element[0]);
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged();
-        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), () => updateTabColor(), { fireImmediately: true });
+        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), updateTabColor, { fireImmediately: true });
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 00415dab1..d086c8b1f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -22,18 +22,18 @@ import { DragManager } from "../../util/DragManager";
 import { Scripting } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
-import AntimodeMenu, { AntimodeMenuProps } from "../AntimodeMenu";
+import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { EditableView } from "../EditableView";
-import GestureOverlay from "../GestureOverlay";
+import { GestureOverlay } from "../GestureOverlay";
 import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocumentView } from "../nodes/DocumentView";
-import RichTextMenu from "../nodes/formattedText/RichTextMenu";
+import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
 import "./CollectionMenu.scss";
 import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
 
 @observer
-export default class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: CollectionMenu;
 
     @observable SelectedCollection: DocumentView | undefined;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index e37644f2f..ac789ab48 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,45 +1,41 @@
 import React = require("react");
-import { action, observable, trace, computed, runInAction } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
 import { CellInfo } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter, Utils, emptyPath, returnEmptyDoclist } from "../../../Utils";
+import { DateField } from "../../../fields/DateField";
 import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { RichTextField } from "../../../fields/RichTextField";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../fields/ScriptField";
+import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
+import { ImageField } from "../../../fields/URLField";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
 import { KeyCodes } from "../../util/KeyCodes";
-import { SetupDrag, DragManager } from "../../util/DragManager";
 import { CompileScript } from "../../util/Scripting";
+import { SearchUtil } from "../../util/SearchUtil";
+import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
-import { MAX_ROW_HEIGHT, COLLECTION_BORDER_WIDTH } from '../globalCssVariables.scss';
+import { undoBatch } from "../../util/UndoManager";
 import '../DocumentDecorations.scss';
 import { EditableView } from "../EditableView";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import "./CollectionSchemaView.scss";
-import { CollectionView, Flyout } from "./CollectionView";
-import { NumCast, StrCast, BoolCast, FieldValue, Cast, DateCast } from "../../../fields/Types";
-import { Docs } from "../../documents/Documents";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faExpand } from '@fortawesome/free-solid-svg-icons';
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { undoBatch } from "../../util/UndoManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { ComputedField } from "../../../fields/ScriptField";
-import { ImageField } from "../../../fields/URLField";
-import { List } from "../../../fields/List";
-import { OverlayView } from "../OverlayView";
+import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../globalCssVariables.scss';
 import { DocumentIconContainer } from "../nodes/DocumentIcon";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import DatePicker from "react-datepicker";
-import "react-datepicker/dist/react-datepicker.css";
-import { DateField } from "../../../fields/DateField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SearchUtil } from "../../util/SearchUtil";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { FieldViewProps } from "../nodes/FieldView";
+import { OverlayView } from "../OverlayView";
+import "./CollectionSchemaView.scss";
+import { CollectionView } from "./CollectionView";
 const path = require('path');
 
-library.add(faExpand);
-
 export interface CellProps {
     row: number;
     col: number;
@@ -287,12 +283,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         if (this.props.isFocused && this.props.isEditable) className += " focused";
         if (this.props.isFocused && !this.props.isEditable) className += " inactive";
 
-
-        // let docExpander = (
-        //     <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
-        //         <FontAwesomeIcon icon="expand" size="sm" />
-        //     </div>
-        // );   
         const positions = [];
         let cfield = props.Document[props.fieldKey];
         this.type = props.fieldKey;
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 4754adc90..a3475b481 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -1,6 +1,5 @@
 import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faGripVertical, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action } from "mobx";
 import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
 import { Doc } from "../../../fields/Doc";
@@ -13,9 +12,6 @@ import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
 import "./CollectionSchemaView.scss";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-library.add(faGripVertical, faTrash);
 
 export interface MovableColumnProps {
     columnRenderer: TableCellRenderer;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index a78061da6..10c33fc4d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,6 +1,4 @@
 import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, untracked } from "mobx";
 import { observer } from "mobx-react";
@@ -9,26 +7,23 @@ import { Resize } from "react-table";
 import "react-table/react-table.css";
 import { Doc, Opt } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
-import { listSpec, makeInterface, emptySchema } from "../../../fields/Schema";
+import { listSpec } from "../../../fields/Schema";
 import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { Cast, NumCast, BoolCast } from "../../../fields/Types";
+import { Cast, NumCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
 import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
 import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 import '../DocumentDecorations.scss';
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { SchemaTable } from "./SchemaTable";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-
-library.add(faCog, faPlus, faSortUp, faSortDown);
-library.add(faTable);
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
 
 export enum ColumnType {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 0a206a6c6..12b7d742b 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,35 +1,31 @@
 import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPalette } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../fields/Doc";
 import { RichTextField } from "../../../fields/RichTextField";
+import { listSpec } from "../../../fields/Schema";
 import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
 import { ScriptField } from "../../../fields/ScriptField";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { ImageField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, setupMoveUpEvents } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DragManager } from "../../util/DragManager";
+import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { CollectionStackingView } from "./CollectionStackingView";
-import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
 import "./CollectionStackingView.scss";
-import { listSpec } from "../../../fields/Schema";
-import { SnappingManager } from "../../util/SnappingManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-library.add(faPalette);
-
 interface CSVFieldColumnProps {
     cols: () => number;
     headings: () => object[];
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4fdf1411d..97336798b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,6 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faEye } from '@fortawesome/free-regular-svg-icons';
-import { faColumns, faCopy, faEllipsisV, faFingerprint, faGlobeAmericas, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable } from 'mobx';
 import { observer } from "mobx-react";
@@ -53,8 +50,6 @@ export const Flyout = higflyout.default;
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 
-library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faGlobeAmericas, faEllipsisV, faImage, faEye as any, faCopy);
-
 export enum CollectionViewType {
     Invalid = "invalid",
     Freeform = "freeform",
@@ -528,7 +523,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}>
                 <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
                     <div className="collectionTimeView-button">
-                        <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                        <FontAwesomeIcon icon={"edit"} size={"lg"} />
                         <span className="collectionTimeView-span">Facet Filters</span>
                     </div>
                 </Flyout>
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 149d4927b..a6de32145 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -1,27 +1,21 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
 import * as React from "react";
-import './ParentDocumentSelector.scss';
 import { Doc } from "../../../fields/Doc";
-import { observer } from "mobx-react";
-import { observable, action, runInAction, trace, computed, reaction, IReactionDisposer } from "mobx";
 import { Id } from "../../../fields/FieldSymbols";
+import { NumCast } from "../../../fields/Types";
 import { SearchUtil } from "../../util/SearchUtil";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { NumCast, StrCast } from "../../../fields/Types";
-import { CollectionViewType } from "./CollectionView";
 import { DocumentButtonBar } from "../DocumentButtonBar";
-import { DocumentManager } from "../../util/DocumentManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
-import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentView } from "../nodes/DocumentView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Tooltip } from "@material-ui/core";
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionViewType } from "./CollectionView";
+import './ParentDocumentSelector.scss';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-library.add(faCog);
-
 type SelectorProps = {
     Document: Doc,
     Stack?: any,
@@ -93,7 +87,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> {
         return <div title="Show Contexts" onPointerDown={e => e.stopPropagation()} className="parentDocumentSelector-linkFlyout">
             <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
                 <span className="parentDocumentSelector-button" >
-                    <FontAwesomeIcon icon={faChevronCircleUp} size={"lg"} />
+                    <FontAwesomeIcon icon={"chevron-circle-up"} size={"lg"} />
                 </span>
             </Flyout>
         </div>;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 02ff3e78c..d64b49c4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,4 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
@@ -15,7 +12,7 @@ import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils, setupMoveUpEvents } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
@@ -24,10 +21,12 @@ import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
 import { InteractionUtils } from "../../../util/InteractionUtils";
+import { LinkManager } from "../../../util/LinkManager";
+import { SearchUtil } from "../../../util/SearchUtil";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { SnappingManager } from "../../../util/SnappingManager";
 import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
@@ -37,20 +36,16 @@ import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
+import { PresBox } from "../../nodes/PresBox";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionViewType } from "../CollectionView";
 import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
-import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { PresBox } from "../../nodes/PresBox";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { LinkManager } from "../../../util/LinkManager";
-
-library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -608,7 +603,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // bcz: theres should be a better way of doing these than referencing these static instances directly
         MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
-        // PDFMenu.Instance.fadeOut(true); (commented out for mobile)
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -1261,7 +1255,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
         !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
-            optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
+            optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: "eye" });
         this.props.ContainingCollectionView &&
             optionItems.push({ description: "Undo Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index a7f44bbbf..1d3604d7f 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -9,13 +9,13 @@ import { InkField } from "../../../../fields/InkField";
 import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { SelectionManager } from "../../../util/SelectionManager";
-import AntimodeMenu, { AntimodeMenuProps } from "../../AntimodeMenu";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
 import "./FormatShapePane.scss";
 import { undoBatch } from "../../../util/UndoManager";
 import { ColorState, SketchPicker } from 'react-color';
 
 @observer
-export default class FormatShapePane extends AntimodeMenu<AntimodeMenuProps> {
+export class FormatShapePane extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: FormatShapePane;
 
     private _lastFill = "#D0021B";
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 5e292a833..46298ec6f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -1,13 +1,12 @@
 import React = require("react");
-import AntimodeMenu, { AntimodeMenuProps } from "../../AntimodeMenu";
-import { observer } from "mobx-react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction } from "../../../../Utils";
-import { undoBatch } from "../../../util/UndoManager";
 import { Tooltip } from "@material-ui/core";
+import { observer } from "mobx-react";
+import { unimplementedFunction } from "../../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
 
 @observer
-export default class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: MarqueeOptionsMenu;
 
     public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 7e02673f4..d161f49f7 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,29 +1,28 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
-import { GetEffectiveAcl } from "../../../../fields/util";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
 import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
+import { GetEffectiveAcl } from "../../../../fields/util";
 import { Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
+import { DocumentManager } from "../../../util/DocumentManager";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { ContextMenu } from "../../ContextMenu";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { PreviewCursor } from "../../PreviewCursor";
+import { CollectionDockingView } from "../CollectionDockingView";
 import { SubCollectionViewProps } from "../CollectionSubView";
 import { CollectionView } from "../CollectionView";
-import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
-import { ContextMenuItem } from "../../ContextMenuItem";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { DocumentManager } from "../../../util/DocumentManager";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
index e128f6aab..1bfc6bdce 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -1,40 +1,35 @@
 import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Checkbox, Tooltip } from "@material-ui/core";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import "./PropertiesView.scss";
-import { observable, action, computed, runInAction } from "mobx";
-import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync, DataSym } from "../../../../fields/Doc";
+import { ColorState, SketchPicker } from "react-color";
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { InkField } from "../../../../fields/InkField";
 import { ComputedField } from "../../../../fields/ScriptField";
-import { EditableView } from "../../EditableView";
-import { KeyValueBox } from "../../nodes/KeyValueBox";
 import { Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView";
-import { returnFalse, returnOne, emptyFunction, emptyPath, returnTrue, returnZero, returnEmptyFilter, Utils, returnEmptyDoclist } from "../../../../Utils";
-import { Id } from "../../../../fields/FieldSymbols";
-import { Transform } from "../../../util/Transform";
-import { PropertiesButtons } from "../../PropertiesButtons";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip, Checkbox } from "@material-ui/core";
-import SharingManager from "../../../util/SharingManager";
+import { GetEffectiveAcl, SharingPermissions } from "../../../../fields/util";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../../Utils";
 import { DocumentType } from "../../../documents/DocumentTypes";
-import { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util";
-import { InkField } from "../../../../fields/InkField";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { SharingManager } from "../../../util/SharingManager";
+import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { ColorState, SketchPicker } from "react-color";
-import "./FormatShapePane.scss";
+import { EditableView } from "../../EditableView";
+import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView";
+import { KeyValueBox } from "../../nodes/KeyValueBox";
 import { PresBox } from "../../nodes/PresBox";
-import { DocumentManager } from "../../../util/DocumentManager";
-import FormatShapePane from "./FormatShapePane";
+import { PropertiesButtons } from "../../PropertiesButtons";
+import { FormatShapePane } from "./FormatShapePane";
+import "./FormatShapePane.scss";
+import "./PropertiesView.scss";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 const _global = (window /* browser */ || global /* node */) as any;
 
-// import * as fa from '@fortawesome/free-solid-svg-icons';
-// import { library } from "@fortawesome/fontawesome-svg-core";
-
-// library.add(fa.faPlus, fa.faMinus, fa.faCog);
-
 interface PropertiesViewProps {
     width: number;
     height: number;
@@ -527,7 +522,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             <div className="inputBox-title"> {title} </div>
             <input className="inputBox-input"
                 type="text" value={value}
-                onChange={e => setter(e.target.value)} />
+                onChange={e => {
+                    setter(e.target.value);
+                }}
+                onKeyPress={e => {
+                    e.stopPropagation();
+                }} />
             <div className="inputBox-button">
                 <div className="inputBox-button-up" key="up2"
                     onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ed64bde32..11a905fb6 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,5 +1,3 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
 import { action, computed, observable } from "mobx";
@@ -12,9 +10,6 @@ import { undoBatch } from "../../util/UndoManager";
 import './LinkEditor.scss';
 import React = require("react");
 
-library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
-
-
 interface GroupTypesDropdownProps {
     groupType: string;
     setGroupType: (group: string) => void;
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 519b78add..31d08edae 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,18 +1,14 @@
-import { action, observable, computed } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { DocumentView } from "../nodes/DocumentView";
-import { LinkEditor } from "./LinkEditor";
-import './LinkMenu.scss';
-import React = require("react");
 import { Doc } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
-import { LinkMenuGroup } from "./LinkMenuGroup";
-import { faTrash } from '@fortawesome/free-solid-svg-icons';
-import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { DocumentView } from "../nodes/DocumentView";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
-
-library.add(faTrash);
+import { LinkEditor } from "./LinkEditor";
+import './LinkMenu.scss';
+import { LinkMenuGroup } from "./LinkMenuGroup";
+import React = require("react");
 
 interface Props {
     docView: DocumentView;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 21c666a4d..a77122456 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,27 +1,23 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { action, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
 import { Cast, StrCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
-import './LinkMenuItem.scss';
-import React = require("react");
-import { DocumentManager } from '../../util/DocumentManager';
-import { setupMoveUpEvents, emptyFunction, Utils, simulateMouseClick } from '../../../Utils';
-import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { DocumentView } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { Hypothesis } from '../../util/HypothesisUtils';
-import { Id } from '../../../fields/FieldSymbols';
-import { Tooltip } from '@material-ui/core';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { undoBatch } from '../../util/UndoManager';
-import { WebField } from '../../../fields/URLField';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
+import './LinkMenuItem.scss';
+import React = require("react");
 
 
 interface LinkMenuItemProps {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index db6d30aac..80d83c3cb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -21,7 +21,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager from '../../util/SharingManager';
+import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 92ca276cb..0d1e063af 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -17,7 +17,7 @@ export interface RectangleTemplate {
 }
 
 @observer
-export default class FaceRectangles extends React.Component<FaceRectanglesProps> {
+export class FaceRectangles extends React.Component<FaceRectanglesProps> {
 
     render() {
         const faces = DocListCast(this.props.document.faces);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5f31f8c8d..410033197 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
@@ -14,7 +11,8 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils';
+import { emptyFunction, returnOne, returnZero, Utils } from '../../../Utils';
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { Networking } from '../../Network';
@@ -24,20 +22,15 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import FaceRectangles from './FaceRectangles';
+import { FaceRectangles } from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 const requestImageSize = require('../../util/request-image-size');
 const path = require('path');
 const { Howl } = require('howler');
 
 
-library.add(faImage, faEye as any, faPaintBrush, faBrain);
-library.add(faFileAudio, faAsterisk);
-
-
 export const pageSchema = createSchema({
     curPage: "number",
     fitWidth: "boolean",
@@ -431,7 +424,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 >
                     <FontAwesomeIcon className="imageBox-audioFont"
                         style={{ color: [DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
-                        icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
+                        icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
                 </div>}
             {this.considerDownloadIcon}
             {this.considerGooglePhotosLink()}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 050ecfc49..1228a285e 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -87,14 +87,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         } else { return undefined; }
     }
     @computed get isPres(): boolean {
+        document.removeEventListener("keydown", this.keyEvents, true);
         if (this.selectedDoc?.type === DocumentType.PRES) {
-            document.removeEventListener("keydown", this.keyEvents, true);
             document.addEventListener("keydown", this.keyEvents, true);
             return true;
-        } else {
-            document.removeEventListener("keydown", this.keyEvents, true);
-            return false;
         }
+        return false;
     }
     @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
 
@@ -373,7 +371,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                         if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = "manual";
                         else if (this.layoutDoc.presLoop) this.startAutoPres(0);
                     }, duration);
-                };
+                }
             }
         };
         this.layoutDoc.presStatus = "auto";
@@ -614,6 +612,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     // Key for when the presentaiton is active
     @action
     keyEvents = (e: KeyboardEvent) => {
+        if (e.target instanceof HTMLInputElement) return;
         let handled = false;
         const anchorNode = document.activeElement as HTMLDivElement;
         if (anchorNode && anchorNode.className?.includes("lm_title")) return;
@@ -629,10 +628,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 handled = true;
             }
         } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back
-            this.back(); if (this._presTimer) clearTimeout(this._presTimer);
+            this.back();
+            if (this._presTimer) clearTimeout(this._presTimer);
             handled = true;
         } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next
-            this.next(); if (this._presTimer) clearTimeout(this._presTimer);
+            this.next();
+            if (this._presTimer) clearTimeout(this._presTimer);
             handled = true;
         } if (e.keyCode === 32) { // spacebar to 'present' or autoplay
             if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0);
@@ -640,9 +641,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             handled = true;
         } if (e.keyCode === 8) { // delete selected items
             if (this.layoutDoc.presStatus === "edit") {
-                this._selectedArray.forEach((doc, i) => {
-                    this.removeDocument(doc);
-                });
+                this._selectedArray.forEach((doc, i) => this.removeDocument(doc));
                 this._selectedArray = [];
                 this._eleArray = [];
                 this._dragArray = [];
@@ -815,7 +814,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                             <div className="ribbon-property">
                                 <input className="presBox-input"
                                     type="number" value={transitionSpeed}
-                                    onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+                                    onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                                     onChange={action((e) => this.setTransitionTime(e.target.value))} /> s
                             </div>
                             <div className="ribbon-propertyUpDown">
@@ -845,7 +844,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                             <div className="ribbon-property">
                                 <input className="presBox-input"
                                     type="number" value={duration}
-                                    onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+                                    onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                                     onChange={action((e) => this.setDurationTime(e.target.value))} /> s
                             </div>
                             <div className="ribbon-propertyUpDown">
@@ -974,7 +973,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                                         <input className="presBox-input"
                                             style={{ textAlign: 'left', width: 50 }}
                                             type="number" value={NumCast(activeItem.presPinViewX)}
-                                            onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+                                            onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                                             onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
                                     </div>
                                 </div>
@@ -984,7 +983,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                                         <input className="presBox-input"
                                             style={{ textAlign: 'left', width: 50 }}
                                             type="number" value={NumCast(activeItem.presPinViewY)}
-                                            onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+                                            onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                                             onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
                                     </div>
                                 </div>
@@ -994,7 +993,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                                         <input className="presBox-input"
                                             style={{ textAlign: 'left', width: 50 }}
                                             type="number" value={NumCast(activeItem.presPinViewScale)}
-                                            onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+                                            onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                                             onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
                                     </div>
                                 </div>
@@ -1044,9 +1043,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                     <div className="ribbon-box">
                         Slide Title: <br></br>
                         <input className="ribbon-textInput" placeholder="..." type="text" name="fname"
-                            onFocus={() => {
-                                document.removeEventListener("keydown", this.keyEvents, true);
-                            }}
+                            onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
                             onChange={(e) => {
                                 e.stopPropagation();
                                 e.preventDefault();
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index bd5b3bff4..8876b4879 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,13 +1,9 @@
 import React = require("react");
-import { observable, action } from "mobx";
-import { observer } from "mobx-react";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observer } from "mobx-react";
 import { UndoManager } from "../../util/UndoManager";
 
-library.add(faAngleRight);
-
 export interface RadialMenuProps {
     description: string;
     event: (stuff?: any) => void;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1cd29d795..866e41ee0 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,12 +1,11 @@
 import React = require("react");
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faVideo } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { Doc } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
+import { InkTool } from "../../../fields/InkField";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast } from "../../../fields/Types";
 import { VideoField } from "../../../fields/URLField";
@@ -18,14 +17,11 @@ import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxBaseComponent } from "../DocComponent";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
-import { InkTool } from "../../../fields/InkField";
 const path = require('path');
 
 type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
 const ScreenshotDocument = makeInterface(documentSchema);
 
-library.add(faVideo);
-
 @observer
 export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
     private _reactionDisposer?: IReactionDisposer;
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 45cdfc5ad..d13680046 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
 import { runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -16,9 +14,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
 import './SliderBox.scss';
 
-
-library.add(faEdit as any);
-
 const SliderSchema = createSchema({
     _sliderMin: "number",
     _sliderMax: "number",
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7d7426e31..f0e3a2b54 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,8 +24,8 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
-import Annotation from "../pdf/Annotation";
-import PDFMenu from "../pdf/PDFMenu";
+import { Annotation } from "../pdf/Annotation";
+import { PDFMenu } from "../pdf/PDFMenu";
 import { PdfViewerMarquee } from "../pdf/PDFViewer";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 77483a179..063cdb0cc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { isEqual } from "lodash";
 import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx";
@@ -22,7 +20,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
 import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
@@ -33,8 +31,8 @@ import { DocumentType } from '../../../documents/DocumentTypes';
 import { DictationManager } from '../../../util/DictationManager';
 import { DragManager } from "../../../util/DragManager";
 import { makeTemplate } from '../../../util/DropConverter';
-import buildKeymap, { updateBullets } from "./ProsemirrorExampleTransfer";
-import RichTextMenu, { RichTextMenuPlugin } from './RichTextMenu';
+import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer";
+import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from "./RichTextRules";
 
 //import { DashDocView } from "./DashDocView";
@@ -61,9 +59,6 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark }
 import React = require("react");
 import { DocumentManager } from '../../../util/DocumentManager';
 
-library.add(faEdit);
-library.add(faSmile, faTextHeight, faUpload);
-
 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)
@@ -915,7 +910,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
                 };
 
-                let start = 0;
+                const start = 0;
                 if (this._editorView && scrollToLinkID) {
                     const editor = this._editorView;
                     const ret = findLinkFrag(editor.state.doc.content, editor);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 8faf752b4..0eb675b4e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -33,7 +33,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
     return tx2;
 };
 
-export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
     const keys: { [key: string]: any } = {};
 
     function bind(key: string, cmd: any) {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 96628949a..a0e2d4351 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,8 +1,8 @@
 import React = require("react");
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, IReactionDisposer, reaction } from "mobx";
+import { Tooltip } from "@material-ui/core";
+import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { lift, wrapIn } from "prosemirror-commands";
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
@@ -11,27 +11,24 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
 import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
+import { Cast, StrCast } from "../../../../fields/Types";
+import { TraceMobx } from "../../../../fields/util";
 import { unimplementedFunction, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
 import { SelectionManager } from "../../../util/SelectionManager";
-import AntimodeMenu, { AntimodeMenuProps } from "../../AntimodeMenu";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
 import { FieldViewProps } from "../FieldView";
 import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import "./RichTextMenu.scss";
 import { schema } from "./schema_rts";
-import { TraceMobx } from "../../../../fields/util";
-import { UndoManager, undoBatch } from "../../../util/UndoManager";
-import { Tooltip } from "@material-ui/core";
 const { toggleMark } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
-
 
 @observer
-export default class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
+export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     static Instance: RichTextMenu;
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
 
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index a455516a3..7e632a0ee 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -9,7 +9,7 @@ import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { wrappingInputRule } from "./prosemirrorPatches";
-import RichTextMenu from "./RichTextMenu";
+import { RichTextMenu } from "./RichTextMenu";
 import { schema } from "./schema_rts";
 import { List } from "../../../../fields/List";
 
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index d29b638e6..98638ecc2 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -6,7 +6,7 @@ import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
 import { DocumentManager } from "../../util/DocumentManager";
-import PDFMenu from "./PDFMenu";
+import { PDFMenu } from "./PDFMenu";
 import "./Annotation.scss";
 
 interface IAnnotationProps {
@@ -19,7 +19,7 @@ interface IAnnotationProps {
 }
 
 @observer
-export default
+export
     class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return DocListCast(this.props.anno.annotations).map(a => (
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 0f7b0a688..32dd376ac 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -1,17 +1,17 @@
 import React = require("react");
-import "./PDFMenu.scss";
-import { observable, action, computed, } from "mobx";
-import { observer } from "mobx-react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction, returnFalse, Utils } from "../../../Utils";
-import AntimodeMenu, { AntimodeMenuProps } from "../AntimodeMenu";
-import { Doc, Opt } from "../../../fields/Doc";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
 import { ColorState } from "react-color";
+import { Doc, Opt } from "../../../fields/Doc";
+import { returnFalse, unimplementedFunction, Utils } from "../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
+import "./PDFMenu.scss";
 
 
 @observer
-export default class PDFMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class PDFMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: PDFMenu;
 
     private _commentCont = React.createRef<HTMLButtonElement>();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 201333d95..c8f98e249 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,39 +1,39 @@
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym, AclAddonly, AclEdit, AclAdmin, DataSym } from "../../../fields/Doc";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
 import { List } from "../../../fields/List";
-import { createSchema, makeInterface, listSpec } from "../../../fields/Schema";
-import { ScriptField, ComputedField } from "../../../fields/ScriptField";
+import { createSchema, makeInterface } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
-import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
+import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { Networking } from "../../Network";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
+import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionView } from "../collections/CollectionView";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
-import Annotation from "./Annotation";
-import PDFMenu from "./PDFMenu";
+import { Annotation } from "./Annotation";
+import { PDFMenu } from "./PDFMenu";
 import "./PDFViewer.scss";
+const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import React = require("react");
-import { SnappingManager } from "../../util/SnappingManager";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
-import { Networking } from "../../Network";
 
 export const pageSchema = createSchema({
     curPage: "number",
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 9b7cf2fc6..f1dd106a7 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -1,28 +1,11 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
+import { DocumentType } from "../../documents/DocumentTypes";
 // import "./SearchBox.scss";
 import "./IconBar.scss";
-import "./IconButton.scss";
-import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as _ from "lodash";
 import { IconButton } from './IconButton';
-import { DocumentType } from "../../documents/DocumentTypes";
-
-
-library.add(faSearch);
-library.add(faObjectGroup);
-library.add(faImage);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-library.add(faMusic);
-library.add(faLink);
-library.add(faChartBar);
-library.add(faGlobeAsia);
-library.add(faBan);
+import "./IconButton.scss";
 
 export interface IconBarProps {
     setIcons: (icons: string[]) => void;
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 52641c543..349690b20 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -1,30 +1,14 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import "./SearchBox.scss";
-import "./IconButton.scss";
-import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library, icon } from '@fortawesome/fontawesome-svg-core';
+import * as _ from "lodash";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { DocumentType } from "../../documents/DocumentTypes";
 import '../globalCssVariables.scss';
-import * as _ from "lodash";
 import { IconBar } from './IconBar';
-import { props } from 'bluebird';
-import { Search } from '../../../server/Search';
-import { gravity } from 'sharp';
-
-library.add(faSearch);
-library.add(faObjectGroup);
-library.add(faImage);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-library.add(faMusic);
-library.add(faLink);
-library.add(faChartBar);
-library.add(faGlobeAsia);
-library.add(faBan);
+import "./IconButton.scss";
+import "./SearchBox.scss";
+import { Font } from '@react-pdf/renderer';
 
 interface IconButtonProps {
     type: string;
@@ -47,59 +31,46 @@ export class IconButton extends React.Component<IconButtonProps>{
     componentDidMount = () => {
         this._resetReaction = reaction(
             () => IconBar.Instance._resetClicked,
-            () => {
+            action(() => {
                 if (IconBar.Instance._resetClicked) {
-                    runInAction(() => {
-                        this.reset();
-                        IconBar.Instance._reset++;
-                        if (IconBar.Instance._reset === 9) {
-                            IconBar.Instance._reset = 0;
-                            IconBar.Instance._resetClicked = false;
-                        }
-                    });
+                    this._isSelected = false;
+                    IconBar.Instance._reset++;
+                    if (IconBar.Instance._reset === 9) {
+                        IconBar.Instance._reset = 0;
+                        IconBar.Instance._resetClicked = false;
+                    }
                 }
-            },
+            }),
         );
+
         this._selectAllReaction = reaction(
             () => IconBar.Instance._selectAllClicked,
-            () => {
+            action(() => {
                 if (IconBar.Instance._selectAllClicked) {
-                    runInAction(() => {
-                        this.select();
-                        IconBar.Instance._select++;
-                        if (IconBar.Instance._select === 9) {
-                            IconBar.Instance._select = 0;
-                            IconBar.Instance._selectAllClicked = false;
-                        }
-                    });
+                    this._isSelected = true;
+                    IconBar.Instance._select++;
+                    if (IconBar.Instance._select === 9) {
+                        IconBar.Instance._select = 0;
+                        IconBar.Instance._selectAllClicked = false;
+                    }
                 }
-            },
+            }),
         );
     }
 
     @action.bound
     getIcon() {
         switch (this.props.type) {
-            case (DocumentType.NONE):
-                return faBan;
-            case (DocumentType.AUDIO):
-                return faMusic;
-            case (DocumentType.COL):
-                return faObjectGroup;
-            case (DocumentType.IMG):
-                return faImage;
-            case (DocumentType.LINK):
-                return faLink;
-            case (DocumentType.PDF):
-                return faFilePdf;
-            case (DocumentType.RTF):
-                return faStickyNote;
-            case (DocumentType.VID):
-                return faVideo;
-            case (DocumentType.WEB):
-                return faGlobeAsia;
-            default:
-                return faCaretDown;
+            case (DocumentType.NONE): return "ban";
+            case (DocumentType.AUDIO): return "music";
+            case (DocumentType.COL): return "object-group";
+            case (DocumentType.IMG): return "image";
+            case (DocumentType.LINK): return "link";
+            case (DocumentType.PDF): return "file-pdf";
+            case (DocumentType.RTF): return "sticky-note";
+            case (DocumentType.VID): return "video";
+            case (DocumentType.WEB): return "globe-asia";
+            default: return "caret-down";
         }
     }
 
@@ -136,53 +107,16 @@ export class IconButton extends React.Component<IconButtonProps>{
         //backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent
     };
 
-    @action.bound
-    public reset() { this._isSelected = false; }
-
-    @action.bound
-    public select() { this._isSelected = true; }
-
-    @action
-    onMouseLeave = () => { this._hover = false; }
-
-    @action
-    onMouseEnter = () => { this._hover = true; }
-
-    getFA = () => {
-        switch (this.props.type) {
-            case (DocumentType.NONE):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faBan} />);
-            case (DocumentType.AUDIO):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
-            case (DocumentType.COL):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
-            case (DocumentType.IMG):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
-            case (DocumentType.LINK):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
-            case (DocumentType.PDF):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
-            case (DocumentType.RTF):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
-            case (DocumentType.VID):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
-            case (DocumentType.WEB):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faGlobeAsia} />);
-            default:
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faCaretDown} />);
-        }
-    }
-
     render() {
         return (
             <div className="type-outer" id={this.props.type + "-filter"}
-                onMouseEnter={this.onMouseEnter}
-                onMouseLeave={this.onMouseLeave}
+                onMouseEnter={() => this._hover = true}
+                onMouseLeave={() => this._hover = false}
                 onClick={this.onClick}>
                 <div className="type-icon" id={this.props.type + "-icon"}
                     style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected}
                 >
-                    {this.getFA()}
+                    <FontAwesomeIcon className="fontawesome-icon" icon={this.getIcon()} />
                 </div>
                 {/* <div className="filter-description">{this.props.type}</div> */}
             </div>
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 647e1ce6f..82c0e19c8 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -1,20 +1,16 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
-import { FieldViewProps, FieldView } from "../nodes/FieldView";
-import { observable, action } from "mobx";
-import { DocumentDecorations } from "../DocumentDecorations";
-import "../../views/nodes/WebBox.scss";
-import "./DashWebRTCVideo.scss";
-import { initialize, hangup, refreshVideos } from "./WebCamLogic";
+import { faPhoneSlash, faSync } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faSync, faPhoneSlash } from "@fortawesome/free-solid-svg-icons";
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
 import { Doc } from "../../../fields/Doc";
 import { InkTool } from "../../../fields/InkField";
-
-library.add(faSync);
-library.add(faPhoneSlash);
+import "../../views/nodes/WebBox.scss";
+import { DocumentDecorations } from "../DocumentDecorations";
+import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import "./DashWebRTCVideo.scss";
+import { hangup, initialize, refreshVideos } from "./WebCamLogic";
+import React = require("react");
 
 
 /**
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 738de09c6..c412059dd 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -7,14 +7,14 @@ import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, re
 import { Doc, Opt } from '../fields/Doc';
 import { Cast, FieldValue } from '../fields/Types';
 import { listSpec } from '../fields/Schema';
-import MainViewModal from '../client/views/MainViewModal';
+import { MainViewModal } from '../client/views/MainViewModal';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { nullAudio } from '../fields/URLField';
 import { Transform } from '../client/util/Transform';
 import { DocumentView } from '../client/views/nodes/DocumentView';
 import { MobileInterface } from './MobileInterface';
 import { DictationOverlay } from '../client/views/DictationOverlay';
-import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
 import { ContextMenu } from '../client/views/ContextMenu';
 
 @observer
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index d21d326f6..0ae952304 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,19 +1,19 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as rp from 'request-promise';
-import { Docs } from '../client/documents/Documents';
-import "./ImageUpload.scss";
-import React = require('react');
 import { DocServer } from '../client/DocServer';
-import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import { Utils } from '../Utils';
+import { Docs } from '../client/documents/Documents';
 import { Networking } from '../client/Network';
+import { MainViewModal } from '../client/views/MainViewModal';
 import { Doc, Opt } from '../fields/Doc';
-import { Cast } from '../fields/Types';
-import { listSpec } from '../fields/Schema';
 import { List } from '../fields/List';
-import MainViewModal from '../client/views/MainViewModal';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { listSpec } from '../fields/Schema';
+import { Cast } from '../fields/Types';
+import { Utils } from '../Utils';
+import "./ImageUpload.scss";
 import { MobileInterface } from './MobileInterface';
+import React = require('react');
 
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 05a695147..8ca67f9ee 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,4 +1,4 @@
-import * as React from "react";
+
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
     faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
@@ -10,34 +10,34 @@ import {
     faAlignRight, faAlignLeft
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, reaction, trace, runInAction } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast } from '../fields/Doc';
-import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../Utils';
+import * as React from "react";
 import { Docs, DocumentOptions } from '../client/documents/Documents';
+import { DocumentType } from "../client/documents/DocumentTypes";
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { Scripting } from '../client/util/Scripting';
-import { DocumentView } from '../client/views/nodes/DocumentView';
+import { SettingsManager } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
-import "./MobileInterface.scss";
-import "./ImageUpload.scss";
-import "./AudioUpload.scss";
-import SettingsManager from '../client/util/SettingsManager';
-import { Uploader } from "./ImageUpload";
+import { UndoManager } from "../client/util/UndoManager";
 import { DockedFrameRenderer } from '../client/views/collections/CollectionDockingView';
-import { InkTool } from '../fields/InkField';
-import GestureOverlay from "../client/views/GestureOverlay";
-import { ScriptField } from "../fields/ScriptField";
+import { CollectionViewType } from "../client/views/collections/CollectionView";
+import { GestureOverlay } from "../client/views/GestureOverlay";
+import { AudioBox } from "../client/views/nodes/AudioBox";
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { RichTextMenu } from "../client/views/nodes/formattedText/RichTextMenu";
 import { RadialMenu } from "../client/views/nodes/RadialMenu";
-import { UndoManager } from "../client/util/UndoManager";
+import { Doc, DocListCast } from '../fields/Doc';
+import { InkTool } from '../fields/InkField';
 import { List } from "../fields/List";
-import { AudioUpload } from "./AudioUpload";
+import { ScriptField } from "../fields/ScriptField";
 import { Cast, FieldValue } from '../fields/Types';
-import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
-import { AudioBox } from "../client/views/nodes/AudioBox";
-import { CollectionViewType } from "../client/views/collections/CollectionView";
-import { DocumentType } from "../client/documents/DocumentTypes";
-import { CollectionFreeFormViewChrome } from "../client/views/collections/CollectionMenu";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
+import { AudioUpload } from "./AudioUpload";
+import { Uploader } from "./ImageUpload";
+import "./AudioUpload.scss";
+import "./ImageUpload.scss";
+import "./MobileInterface.scss";
 
 library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -47,6 +47,7 @@ library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngl
     faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
     faAlignLeft, faAlignRight);
 
+
 @observer
 export class MobileInterface extends React.Component {
     static Instance: MobileInterface;
-- 
cgit v1.2.3-70-g09d2


From 1536f8d6e5f44fd14954550e9bd670561727b417 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Aug 2020 15:45:07 -0400
Subject: fixed undo for collection tabs. fixed treeview to support
 freezeChildren to prevent children from being removed from the tree.  Made
 _isBackground a layoutfield.  fixed formattedTextBox exceptions on undo from
 a tab.

---
 src/client/documents/Documents.ts                  |  16 +-
 src/client/util/CurrentUserUtils.ts                |  19 +-
 src/client/views/DocComponent.tsx                  |   6 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/animationtimeline/Keyframe.tsx    |   2 +-
 .../views/collections/CollectionDockingView.tsx    | 316 ++++++++-------------
 .../views/collections/CollectionTreeView.tsx       |  18 +-
 src/client/views/collections/CollectionView.tsx    |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  16 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   8 +-
 .../collectionFreeForm/PropertiesView.tsx          |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  18 +-
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +-
 src/client/views/nodes/PresBox.tsx                 |  12 +-
 src/client/views/nodes/VideoBox.tsx                |   3 +-
 src/client/views/nodes/WebBox.tsx                  |   8 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  16 +-
 .../views/presentationview/PresElementBox.tsx      |   2 +-
 src/client/views/search/SearchBox.tsx              |   2 +-
 src/fields/Doc.ts                                  |  22 +-
 src/fields/documentSchemas.ts                      |   2 +-
 23 files changed, 231 insertions(+), 275 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b8cac2beb..18ff993fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -33,7 +33,6 @@ import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
 import { DocHolderBox } from "../views/nodes/DocHolderBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
-import { MenuIconBox } from "../views/nodes/MenuIconBox";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
 import { KeyValueBox } from "../views/nodes/KeyValueBox";
@@ -129,7 +128,7 @@ export interface DocumentOptions {
     isAnnotating?: boolean; // whether we web document is annotation mode where links can't be clicked to allow annotations to be created
     opacity?: number;
     defaultBackgroundColor?: string;
-    isBackground?: boolean;
+    _isBackground?: boolean;
     isLinkButton?: boolean;
     _columnWidth?: number;
     _fontSize?: string;
@@ -186,6 +185,7 @@ export interface DocumentOptions {
     strokeWidth?: number;
     cloneFieldFilter?: List<string>; // fields not to copy when the document is cloned
     _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
+    freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
     treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
     treeViewHideTitle?: boolean; // whether to hide the top document of a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
@@ -535,7 +535,7 @@ export namespace Docs {
 
         Scripting.addGlobal(Buxton);
 
-        const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
+        const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "removeDropProperties", "treeViewOpen"];
 
         /**
          * This function receives the relevant document prototype and uses
@@ -684,7 +684,7 @@ export namespace Docs {
             const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, {
                 dontRegisterChildViews: true,
                 isLinkButton: true, treeViewHideTitle: true, backgroundColor: "lightBlue", // lightBlue is default color for linking dot and link documents text comment area
-                treeViewExpandedView: "fields", removeDropProperties: new List(["isBackground", "isLinkButton"]), ...options
+                treeViewExpandedView: "fields", removeDropProperties: new List(["_isBackground", "isLinkButton"]), ...options
             }, id);
             const linkDocProto = Doc.GetProto(doc);
             linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document. 
@@ -694,7 +694,7 @@ export namespace Docs {
             linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc.displayTimecode;
 
             if (linkDocProto.linkBoxExcludedKeys === undefined) {
-                Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
+                Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]);
                 Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
             }
 
@@ -825,9 +825,9 @@ export namespace Docs {
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
-            const tabs = TreeDocument(documents, { title: "On-Screen Tabs", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
-            const all = TreeDocument([], { title: "Off-Screen Tabs", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
+            const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
             Doc.GetProto(inst).data = new List<Doc>([tabs, all]);
             return inst;
         }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2c9c870aa..c60403701 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -26,6 +26,8 @@ import { LinkManager } from "./LinkManager";
 import { Id } from "../../fields/FieldSymbols";
 import { HistoryUtil } from "./History";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { SelectionManager } from "./SelectionManager";
+import { DocumentManager } from "./DocumentManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -1018,12 +1020,9 @@ export class CurrentUserUtils {
     }
 
     public static snapshotDashboard = (userDoc: Doc) => {
-        const activeDashboard = Cast(userDoc.activeDashboard, Doc, null);
-        CollectionDockingView.Copy(activeDashboard).then(copy => {
-            Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
-            // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-            setTimeout(() => CurrentUserUtils.openDashboard(userDoc, copy), 0);
-        });
+        const copy = CollectionDockingView.Copy(Cast(userDoc.activeDashboard, Doc, null));
+        Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
+        CurrentUserUtils.openDashboard(userDoc, copy);
     }
 
     public static createNewDashboard = async (userDoc: Doc, id?: string) => {
@@ -1059,6 +1058,14 @@ export class CurrentUserUtils {
     }
 }
 
+Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
+    const copy = Doc.copyDragFactory(dragFactory);
+    if (copy) {
+        CollectionDockingView.AddRightSplit(copy);
+        const view = DocumentManager.Instance.getFirstDocumentView(copy);
+        view && SelectionManager.SelectDoc(view, false);
+    }
+});
 Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
     "creates a snapshot copy of a dashboard");
 Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); },
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 1cea94064..e9469d7bb 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -58,7 +58,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
 
         lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
 
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);//  && !Doc.SelectedTool();  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document._isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);//  && !Doc.SelectedTool();  // bcz: inking state shouldn't affect static tools 
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -187,9 +187,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         }
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
-        active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.props.Document.isBackground) &&
+        active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.props.Document._) &&
             (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0 || BoolCast((this.layoutDoc as any).forceActive)) ? true : false)
-        annotationsActive = (outsideReaction?: boolean) => (Doc.GetSelectedTool() !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
+        annotationsActive = (outsideReaction?: boolean) => (Doc.GetSelectedTool() !== InkTool.None || (this.props.Document._isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
     return Component;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9371728ce..3afda13c2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -386,7 +386,7 @@ export class MainView extends React.Component {
     addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
         return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
             doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) :
-                CollectionDockingView.AddRightSplit(doc, libraryPath);
+                CollectionDockingView.AddRightSplit(doc);
     }
     sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0);
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 1b81c544a..4fb362ab1 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -331,7 +331,7 @@ export class Keyframe extends React.Component<IProps> {
         }),
             TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
                 const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
-                CollectionDockingView.AddRightSplit(kvp, emptyPath);
+                CollectionDockingView.AddRightSplit(kvp);
             })),
             TimelineMenu.Instance.addItem("button", "Delete", action(() => {
                 (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3d8ac2a23..86d27acd5 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,7 +24,7 @@ import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import { PresBox } from '../nodes/PresBox';
 import "./CollectionDockingView.scss";
@@ -59,7 +59,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
 
     @observable private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
-    private _flush: boolean = false;
+    private _flush: UndoManager.Batch | undefined;
     private _ignoreStateChange = "";
     private _isPointerDown = false;
     private _maximizedSrc: Opt<DocumentView>;
@@ -73,6 +73,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
+        console.log("START drag batch");
+        !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
         const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
             {
                 type: 'row',
@@ -104,6 +106,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         SelectionManager.DeselectAll();
     }
 
+    @undoBatch
     public CloseFullScreen = () => {
         const target = this._goldenLayout._maximisedItem;
         if (target !== null && this._maximizedSrc) {
@@ -121,25 +124,24 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     public static CloseRightSplit(document: Opt<Doc>): boolean {
-        const instance = CollectionDockingView.Instance;
         const tryClose = (childItem: any) => {
             if (childItem.config?.component === "DocumentFrameRenderer") {
                 const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId);
                 if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) {
                     childItem.remove();
-                    instance.layoutChanged(document);
                     return true;
                 }
             }
             return false;
         };
-        const retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
+        const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false :
+            Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
 
-        retVal && instance.stateChanged();
+        retVal && CollectionDockingView.Instance.layoutChanged(document);
         return retVal;
     }
 
+    @undoBatch
     @action
     layoutChanged(removed?: Doc) {
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
@@ -147,20 +149,17 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
     }
-    @undoBatch
-    @action
+
     public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
         if (!CollectionDockingView.Instance) return false;
         const instance = CollectionDockingView.Instance;
-        let retVal = false;
-        if (instance._goldenLayout.root.contentItems[0].isRow) {
-            retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+        const retVal = !instance._goldenLayout.root.contentItems[0].isRow ? false :
+            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
                     DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
                     const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
                     child.addChild(newItemStackConfig, undefined);
                     !addToSplit && child.contentItems[0].remove();
-                    instance.layoutChanged(document);
                     return true;
                 }
                 return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
@@ -168,16 +167,12 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                         const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
                         child.addChild(newItemStackConfig, undefined);
                         !addToSplit && child.contentItems[j].remove();
-                        instance.layoutChanged(document);
                         return true;
                     }
                     return false;
                 });
             });
-        }
-        if (retVal) {
-            instance.stateChanged();
-        }
+        retVal && instance.layoutChanged(document);
         return retVal;
     }
 
@@ -186,34 +181,51 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     @undoBatch
     @action
-    public static AddRightSplit(document: Doc, libraryPath?: Doc[]) {
+    public static AddRightSplit(document: Doc, dontSelect: boolean = false, isDisplayPanel: Opt<boolean> = undefined) {
         if (!CollectionDockingView.Instance) return false;
-        if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
-        const instance = CollectionDockingView.Instance;
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
-        };
 
-        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-        if (instance._goldenLayout.root.contentItems.length === 0) {
-            instance._goldenLayout.root.addChild(newContentItem);
-        } else if (instance._goldenLayout.root.contentItems[0].isRow) {
-            instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+        const ind = CollectionDockingView.Instance._tabMap.findIndex((val) => val.doc === document);
+        if (ind !== -1) {
+            const tab = CollectionDockingView.Instance._tabMap[ind].tab;
+            const activeContentItem = tab.header.parent.getActiveContentItem();
+            if (tab.contentItem !== activeContentItem) {
+                tab.header.parent.setActiveContentItem(tab.contentItem);
+            }
+            tab.setActive(true);
         } else {
-            const collayout = instance._goldenLayout.root.contentItems[0];
-            const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-            collayout.parent.replaceChild(collayout, newRow);
+            document.isDisplayPanel = isDisplayPanel;
 
-            newRow.addChild(newContentItem, undefined, true);
-            newRow.addChild(collayout, 0, true);
+            if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
+            const instance = CollectionDockingView.Instance;
+            const newItemStackConfig = {
+                type: 'stack',
+                content: [CollectionDockingView.makeDocumentConfig(document, undefined, [])]
+            };
+
+            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
 
-            collayout.config.width = 50;
-            newContentItem.config.width = 50;
+            if (instance._goldenLayout.root.contentItems.length === 0) {
+                instance._goldenLayout.root.addChild(newContentItem);
+            } else if (instance._goldenLayout.root.contentItems[0].isRow) {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else {
+                const collayout = instance._goldenLayout.root.contentItems[0];
+                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                collayout.parent.replaceChild(collayout, newRow);
+
+                newRow.addChild(newContentItem, undefined, true);
+                newRow.addChild(collayout, 0, true);
+
+                collayout.config.width = 50;
+                newContentItem.config.width = 50;
+            }
+            newContentItem.callDownwards('_$init');
+            instance.layoutChanged();
+        }
+        if (!dontSelect) {
+            const view = DocumentManager.Instance.getFirstDocumentView(document);
+            view && SelectionManager.SelectDoc(view, false);
         }
-        newContentItem.callDownwards('_$init');
-        instance.layoutChanged();
         return true;
     }
 
@@ -221,7 +233,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
-    @undoBatch
     @action
     public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
         if (!CollectionDockingView.Instance) return false;
@@ -289,19 +300,13 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
-    @undoBatch
-    @action
     public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
-        document.isDisplayPanel = true;
         if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            CollectionDockingView.AddRightSplit(document, libraryPath);
+            CollectionDockingView.AddRightSplit(document, false, true);
         }
     }
 
-    @undoBatch
-    @action
     public AddTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        Doc.GetProto(document).lastOpened = new DateField;
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
         if (stack === undefined) {
             let stack: any = this._goldenLayout.root;
@@ -324,10 +329,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    @undoBatch
-    @action
     public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        Doc.GetProto(document).lastOpened = new DateField;
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
         if (stack === undefined) {
             let stack: any = this._goldenLayout.root;
@@ -350,9 +352,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    setupGoldenLayout() {
+    _tabMap: { tab: any, doc: Doc }[] = [];
+
+    async setupGoldenLayout() {
         const config = StrCast(this.props.Document.dockingConfig);
         if (config) {
+            const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+            const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? [];
+            await Promise.all(docids.map(id => DocServer.GetRefField(id)));
+
             if (!this._goldenLayout) {
                 runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             }
@@ -361,7 +369,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                     return;
                 }
                 try {
-                    this._goldenLayout.unbind('itemDropped', this.itemDropped);
                     this._goldenLayout.unbind('tabCreated', this.tabCreated);
                     this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
                     this._goldenLayout.unbind('stackCreated', this.stackCreated);
@@ -369,12 +376,12 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 this._goldenLayout.destroy();
                 runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             }
-            this._goldenLayout.on('itemDropped', this.itemDropped);
             this._goldenLayout.on('tabCreated', this.tabCreated);
             this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
             this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
             this._goldenLayout.container = this._containerRef.current;
+            //this._goldenLayout.on("stateChanged", () => console.log("STATE CHANGED"));
             if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
                 try {
                     this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
@@ -388,46 +395,39 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     reactionDisposer?: Lambda;
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
-            const observer = new _global.ResizeObserver(action((entries: any) => {
-                for (const entry of entries) {
-                    this.onResize(null as any);
-                }
-            }));
+            const observer = new _global.ResizeObserver(this.onResize);
             observer.observe(this._containerRef.current);
             this.reactionDisposer = reaction(
-                () => this.props.Document.dockingConfig,
-                () => {
-                    if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
-                        // Because this is in a set timeout, if this component unmounts right after mounting,
-                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it
-                        setTimeout(() => this.setupGoldenLayout(), 1);
+                () => StrCast(this.props.Document.dockingConfig),
+                config => {
+                    if (!this._goldenLayout || this._ignoreStateChange !== config) {
+                        this.setupGoldenLayout();
                         DocListCast((Doc.UserDoc().myDashboards as Doc).data).map(d => d.dashboardBrush = false);
                         this.props.Document.dashboardBrush = true;
                     }
                     this._ignoreStateChange = "";
-                }, { fireImmediately: true });
-
+                });
+            setTimeout(() => this.setupGoldenLayout(), 0);
             window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
         }
     }
     componentWillUnmount: () => void = () => {
         try {
             this.props.Document.dashboardBrush = false;
-            this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
         } catch (e) {
 
         }
-        this._goldenLayout && this._goldenLayout.destroy();
+        this._goldenLayout?.destroy();
         runInAction(() => {
             CollectionDockingView.Instances.splice(CollectionDockingView.Instances.indexOf(this), 1);
             this._goldenLayout = null;
         });
         window.removeEventListener('resize', this.onResize);
 
-        this.reactionDisposer && this.reactionDisposer();
+        this.reactionDisposer?.();
     }
     @action
     onResize = (event: any) => {
@@ -437,26 +437,26 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     @action
-    onPointerUp = (e: React.PointerEvent): void => {
+    onPointerUp = (e: MouseEvent): void => {
+        window.removeEventListener("pointerup", this.onPointerUp);
+        this._isPointerDown = false;
         if (this._flush) {
-            this._flush = false;
             setTimeout(() => {
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
                 this.stateChanged();
+                console.log("END BATCH Up");
+                this._flush!.end();
+                this._flush = undefined;
             }, 10);
         }
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._isPointerDown = true;
-        const onPointerUp = action(() => {
-            window.removeEventListener("pointerup", onPointerUp);
-            this._isPointerDown = false;
-        });
-        window.addEventListener("pointerup", onPointerUp);
-        const className = (e.target as any).className;
-        if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
-            this._flush = true;
+        window.addEventListener("mouseup", this.onPointerUp);
+
+        if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
+            console.log("START BATCH dwn");
+            this._flush = UndoManager.StartBatch("golden layout edit");
         }
         if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
             return;
@@ -464,29 +464,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             e.stopPropagation();
         }
     }
-
-    updateDataField = async (json: string) => {
-        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
-        const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
-
-        const docs = !docids ? [] : (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
-        const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
-        const tabs = Cast(sublists[0], Doc, null);
-        const other = Cast(sublists[1], Doc, null);
-        const tabdocs = DocListCast(tabs.data);
-        const otherdocs = DocListCast(other.data);
-        Doc.GetProto(tabs).data = new List<Doc>(docs);
-        const otherSet = new Set<Doc>();
-        otherdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-        tabdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-        Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
-    }
-
-    public static async Copy(doc: Doc) {
+    public static Copy(doc: Doc) {
         let json = StrCast(doc.dockingConfig);
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
         const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || [];
-        const docs = (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
+        const docs = docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
         const newtabs = docs.map(doc => {
             const copy = Doc.MakeAlias(doc);
             json = json.replace(doc[Id], copy[Id]);
@@ -503,34 +485,36 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return copy;
     }
 
-    @undoBatch
+    @action
     stateChanged = () => {
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.dockingConfig = json;
-        this.updateDataField(json);
-    }
-
-    itemDropped = () => {
-        CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-        this.stateChanged();
-    }
+        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+        const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
+        const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
 
-    htmlToElement(html: string) {
-        const template = document.createElement('template');
-        html = html.trim(); // Never return a text node of whitespace as the result
-        template.innerHTML = html;
-        return template.content.firstChild;
+        this.props.Document.dockingConfig = json;
+        const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
+        const tabs = Cast(sublists[0], Doc, null);
+        const other = Cast(sublists[1], Doc, null);
+        const tabdocs = DocListCast(tabs.data);
+        const otherdocs = DocListCast(other.data);
+        Doc.GetProto(tabs).data = new List<Doc>(docs);
+        const otherSet = new Set<Doc>();
+        otherdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+        tabdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+        Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
     }
 
-    tabCreated = async (tab: any) => {
+    tabCreated = (tab: any) => {
         tab.titleElement[0].Tab = tab;
         if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
             if (tab.contentItem.config.fixed) {
                 tab.contentItem.parent.config.fixed = true;
             }
 
-            const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
+            const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId) as Doc;
             if (doc instanceof Doc) {
+                this._tabMap.push({ tab: tab, doc: doc });
                 tab.titleElement[0].onclick = (e: any) => {
                     if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
                     tab.titleElement[0].lastClick = Date.now();
@@ -550,8 +534,10 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 gearSpan.style.paddingRight = "12px";
                 const stack = tab.contentItem.parent;
                 tab.element[0].onpointerdown = (e: any) => {
-                    const view = DocumentManager.Instance.getDocumentView(doc);
-                    view && SelectionManager.SelectDoc(view, false);
+                    if (e.target.className !== "lm_close_tab") {
+                        const view = DocumentManager.Instance.getDocumentView(doc);
+                        view && SelectionManager.SelectDoc(view, false);
+                    }
                 };
                 // shifts the focus to this tab when another tab is dragged over it
                 tab.element[0].onmouseenter = (e: any) => {
@@ -594,27 +580,23 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 tab.reactComponents = [gearSpan];
                 tab.element.append(gearSpan);
                 tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
-                    tab.titleElement[0].textContent = title, { fireImmediately: true };
+                    tab.titleElement[0].value = title;
                     tab.titleElement[0].style.padding = degree ? 0 : 2;
                     tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
-                });
+                }, { fireImmediately: true });
                 //TODO why can't this just be doc instead of the id?
                 tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
             }
         }
         tab.closeElement.off('click') //unbind the current click handler
-            .click(async function () {
+            .click(function () {
                 tab.selectionDisposer?.();
                 tab.reactionDisposer?.();
                 tab.buttonDisposer?.();
-                const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
+                const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId);
                 if (doc instanceof Doc) {
-                    const theDoc = doc;
-
-                    const recent = await Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc);
-                    if (recent) {
-                        Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                    }
+                    const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
+                    recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     SelectionManager.DeselectAll();
                 }
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
@@ -624,6 +606,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     tabDestroyed = (tab: any) => {
+        const ind = this._tabMap.findIndex((val) => val.tab === tab);
+        ind !== -1 && this._tabMap.splice(ind, 1);
         if (tab.reactComponents) {
             for (const ele of tab.reactComponents) {
                 ReactDOM.unmountComponentAtNode(ele);
@@ -631,15 +615,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         }
     }
 
-    stackActiveChanged = () => {
-        try {
-            CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-            this.stateChanged();
-        } catch (e) { } // catch exception thrown when config has not been initialzed yet
-    }
 
     stackCreated = (stack: any) => {
-        stack.layoutManager.on("activeContentItemChanged", this.stackActiveChanged);
         //stack.header.controlsContainer.find('.lm_popout').hide();
         stack.header.element.on('mousedown', (e: any) => {
             if (e.target === stack.header.element[0] && e.button === 2) {
@@ -651,58 +628,28 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             }
         });
 
-        // starter code for bezel to add new pane
-        // stack.element.on("touchstart", (e: TouchEvent) => {
-        // if (e.targetTouches.length === 2) {
-        //     let pt1 = e.targetTouches.item(0);
-        //     let pt2 = e.targetTouches.item(1);
-        //     let threshold = 40 * window.devicePixelRatio;
-        //     if (pt1 && pt2 && InteractionUtils.TwoPointEuclidist(pt1, pt2) < threshold) {
-        //         let edgeThreshold = 30 * window.devicePixelRatio;
-        //         let center = InteractionUtils.CenterPoint([pt1, pt2]);
-        //         let stackRect: DOMRect = stack.element.getBoundingClientRect();
-        //         let nearLeft = center.X - stackRect.x < edgeThreshold;
-        //         let nearTop = center.Y - stackRect.y < edgeThreshold;
-        //         let nearRight = stackRect.right - center.X < edgeThreshold;
-        //         let nearBottom = stackRect.bottom - center.Y < edgeThreshold;
-        //         let ns = [nearLeft, nearTop, nearRight, nearBottom].filter(n => n);
-        //         if (ns.length === 1) {
-
-        //         }
-        //     }
-        // }
-        // });
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
-            .click(action(async () => {
+            .click(action(() => {
                 //if (confirm('really close this?')) {
-
                 stack.remove();
-                stack.contentItems.forEach(async (contentItem: any) => {
-                    const doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+                stack.contentItems.forEach((contentItem: any) => {
+                    const doc = Cast(DocServer.GetCachedRefField(contentItem.config.props.documentId), Doc, null);
                     if (doc instanceof Doc) {
-                        let recent: Doc | undefined;
-                        if (recent = await Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc)) {
-                            Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                        }
-                        const theDoc = doc;
+                        const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
+                        recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     }
                 });
-                //}
             }));
         stack.header.controlsContainer.find('.lm_popout') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(() => {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
-
                 const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null);
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
                 this.AddTab(stack, Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
                 }));
-
-                // const url = Utils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
-                // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
     }
 
@@ -710,10 +657,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         if (this.props.renderDepth > 0) {
             return <div style={{ width: "100%", height: "100%" }}>Nested dashboards can't be rendered</div>;
         }
-        return <div className="collectiondockingview-container" id="menuContainer"
-            onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
+        return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />;
     }
-
 }
 
 interface DockedFrameProps {
@@ -725,7 +670,6 @@ interface DockedFrameProps {
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     _mainCont: HTMLDivElement | null = null;
-    @observable private _libraryPath: Doc[] = [];
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
@@ -741,17 +685,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     constructor(props: any) {
         super(props);
-        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
-        this.props.libraryPath && this.setupLibraryPath();
-    }
-
-    async setupLibraryPath() {
-        Promise.all(this.props.libraryPath.map(async docid => {
-            const d = await DocServer.GetRefField(docid);
-            return d instanceof Doc ? d : undefined;
-        })).then(action((list: (Doc | undefined)[]) => this._libraryPath = list.filter(d => d).map(d => d as Doc)));
+        this._document = Cast(DocServer.GetCachedRefField(this.props.documentId), Doc, null);
     }
-
     /**
      * Adds a document to the presentation view
      **/
@@ -792,7 +727,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     componentDidMount() {
         const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
-        const selected = () => SelectionManager.SelectedDocuments().some(v => Doc.AreProtosEqual(v.props.Document, this._document));
+        const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
         const updateTabColor = () => this._tab && (this._tab.style.backgroundColor = selected() ? color() : "");
         const observer = new _global.ResizeObserver(action((entries: any) => {
             for (const entry of entries) {
@@ -820,6 +755,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document);
                 dv && SelectionManager.SelectDoc(dv, false);
             });
+            (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
     }
@@ -873,7 +809,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (doc._viewType === CollectionViewType.Docking) {
             return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
         } else if (location === "onRight") {
-            return CollectionDockingView.AddRightSplit(doc, libraryPath);
+            return CollectionDockingView.AddRightSplit(doc);
         } else if (location === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
         } else if (location === "replace") {
@@ -968,7 +904,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
         return <>
             <DocumentView key={document[Id]}
-                LibraryPath={this._libraryPath}
+                LibraryPath={emptyPath}
                 Document={document}
                 DataDoc={resolvedDataDoc}
                 bringToFront={emptyFunction}
@@ -1008,6 +944,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div >);
     }
 }
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
-    "opens up the inputted document on the right side of the screen", "(doc: any)");
+Scripting.addGlobal(function openOnRight(doc: any, dontSelect: boolean = false) { CollectionDockingView.AddRightSplit(doc, dontSelect); },
+    "opens up the inputted document on the right side of the screen", "(doc: any, dontSelect: boolean)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 66bdcf130..cdfa67769 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -39,7 +39,7 @@ export interface TreeViewProps {
     containingCollection: Doc;
     prevSibling?: Doc;
     renderDepth: number;
-    deleteDoc: (doc: Doc | Doc[]) => boolean;
+    removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined;
     moveDocument: DragManager.MoveFunction;
     dropAction: dropActionType;
     addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
@@ -119,14 +119,14 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight");
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-        return this.doc !== target && this.props.deleteDoc(doc) && addDoc(doc);
+        return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc);
     }
     @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
         return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
     }
     @undoBatch @action removeDoc = (doc: Doc | Doc[]) => {
         return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
-            flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true);
+            flg && Doc.RemoveDocFromList(this.doc, Doc.LayoutFieldKey(this.doc), doc), true);
     }
 
     constructor(props: any) {
@@ -280,8 +280,8 @@ class TreeView extends React.Component<TreeViewProps> {
                 const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
                 const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce(
                     (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true);
-                contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
-                    DocListCast(contents), this.props.treeView, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
+                contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents),
+                    this.props.treeView, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
                     this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                     [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged);
@@ -458,7 +458,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 onDoubleClick={this.onChildDoubleClick}
                 dropAction={this.props.dropAction}
                 moveDocument={this.move}
-                removeDocument={this.removeDoc}
+                removeDocument={this.props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
                 ContentScaling={returnOne}
                 PanelWidth={this.truncateTitleWidth}
@@ -612,7 +612,7 @@ class TreeView extends React.Component<TreeViewProps> {
             }
 
             const indent = i === 0 ? undefined : () => {
-                if (StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) {
+                if (remove && StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) {
                     const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1];
                     const fieldKey = fieldKeysub.split("\'")[1];
                     if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
@@ -623,7 +623,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 }
             };
             const outdent = !parentCollectionDoc ? undefined : () => {
-                if (StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
+                if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
                     const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1];
                     const fieldKey = fieldKeysub.split("\'")[1];
                     Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false);
@@ -651,7 +651,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 onCheckedClick={onCheckedClick}
                 onChildClick={onChildClick}
                 renderDepth={renderDepth}
-                deleteDoc={remove}
+                removeDoc={StrCast(containingCollection.freezeChildren).includes("remove") ? undefined : remove}
                 addDocument={addDocument}
                 backgroundColor={backgroundColor}
                 panelWidth={rowWidth}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f028b797c..1da0e601a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -582,10 +582,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutTemplate: this.childLayoutTemplate,
             ChildLayoutString: this.childLayoutString,
         };
-        const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
+        const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document._isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
             `${Cast(Doc.UserDoc().activeDashboard, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
-            style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}>
+            style={{ pointerEvents: this.props.Document._isBackground ? "none" : undefined, boxShadow }}>
             {this.showIsTagged()}
             <div className="collectionView-facetCont" style={{ display: this.props.PanelPosition === "absolute" ? "flex" : "", justifyContent: this.props.PanelPosition === "absolute" ? "center" : "", width: `calc(100% - ${this.facetWidth()}px)` }}>
                 {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7ba8694ed..b55dc467a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -217,7 +217,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const nd = [NumCast(layoutDoc._nativeWidth), NumCast(layoutDoc._nativeHeight)];
             layoutDoc._width = NumCast(layoutDoc._width, 300);
             layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
-            d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+            d._isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -256,7 +256,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        // if (this.props.Document.isBackground) return false;
+        // if (this.props.Document._isBackground) return false;
         const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
         if (this.isAnnotationOverlay !== true && de.complete.linkDragData) {
             return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
@@ -384,8 +384,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 clusterColor = colors[cluster % colors.length];
                 const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
                 // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
-                set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
+                set && set.filter(s => !s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
+                set && set.filter(s => s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
             }
         }
         return clusterColor;
@@ -839,7 +839,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     bringToFront = action((doc: Doc, sendToBack?: boolean) => {
-        if (sendToBack || doc.isBackground) {
+        if (sendToBack || doc._isBackground) {
             doc.zIndex = 0;
         } else if (doc.isInkMask) {
             doc.zIndex = 5000;
@@ -935,7 +935,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
-    @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+    @computed get backgroundActive() { return this.layoutDoc._isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     backgroundHalo = () => BoolCast(this.Document._useClusters);
@@ -1328,7 +1328,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         };
         const snappableDocs: Doc[] = [];  // the set of documents in the visible viewport that we will try to snap to;
         const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
-        this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect));  // first see if there are any foreground docs to snap to
+        this.getActiveDocuments().filter(doc => !doc._isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect));  // first see if there are any foreground docs to snap to
         !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to
         !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs
 
@@ -1422,7 +1422,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SnappingManager.GetIsDragging(); }
+    @computed get backgroundEvents() { return this.layoutDoc._isBackground && SnappingManager.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 864c45ca2..759d142ea 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -355,7 +355,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.hideMarquee();
     }
 
-    getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
+    getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, _isBackground?: boolean) => {
         const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => {
             Doc.GetProto(doc).data = new List<Doc>(selected);
             Doc.GetProto(doc).title = "nested freeform";
@@ -363,8 +363,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             return doc;
         })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
         newCollection.system = undefined;
-        newCollection.isBackground = isBackground;
-        newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined;
+        newCollection._isBackground = _isBackground;
+        newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : _isBackground ? "cyan" : undefined;
         newCollection._width = this.Bounds.width;
         newCollection._height = this.Bounds.height;
         newCollection.x = this.Bounds.left;
@@ -682,7 +682,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     marqueeSelect(selectBackgrounds: boolean = true) {
         const selRect = this.Bounds;
         const selection: Doc[] = [];
-        this.props.activeDocuments().filter(doc => !doc.isBackground && !doc.z).map(doc => {
+        this.props.activeDocuments().filter(doc => !doc._isBackground && !doc.z).map(doc => {
             const layoutDoc = Doc.Layout(doc);
             const x = NumCast(doc.x);
             const y = NumCast(doc.y);
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
index f9ffb4260..c316e1aad 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -148,7 +148,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     &nbsp;
                 </div>);
                 } else {
-                    let contentElement = <EditableView key="editableView"
+                    const contentElement = <EditableView key="editableView"
                         contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
                         height={13}
                         fontSize={10}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 48f10ba26..16b2f7c2a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -252,8 +252,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 boxShadow:
                     this.Opacity === 0 ? undefined :  // if it's not visible, then no shadow
                         this.layoutDoc.z ? `#9c9396  ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                            this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
-                                this.layoutDoc.isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
+                            this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc._isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                                this.layoutDoc._isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
                                     StrCast(this.layoutDoc.boxShadow, ""),
                 borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
                 outline: this.Highlight ? "orange solid 2px" : "",
@@ -264,7 +264,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 zIndex: this.ZInd,
                 mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
+                pointerEvents: this.props.Document._isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
             }} >
 
             {Doc.UserDoc().renderStyle !== "comic" ? (null) :
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 30416b280..eb6988362 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -292,7 +292,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
+            !this.props.Document._isBackground && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
                     clearTimeout(this._timeout);
@@ -694,9 +694,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     toggleBackground = () => {
-        this.Document.isBackground = (this.Document.isBackground ? undefined : true);
-        this.Document._overflow = this.Document.isBackground ? "visible" : undefined;
-        if (this.Document.isBackground) {
+        this.Document._isBackground = (this.Document._isBackground ? undefined : true);
+        this.Document._overflow = this.Document._isBackground ? "visible" : undefined;
+        if (this.Document._isBackground) {
             this.props.bringToFront(this.props.Document, true);
             this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
             this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = this.Document[HeightSym]();
@@ -795,7 +795,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const collectionAcl = GetEffectiveAcl(this.props.ContainingCollectionDoc?.[DataSym]);
-        if (collectionAcl === AclAdmin || collectionAcl === AclEdit) moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+        if ((collectionAcl === AclAdmin || collectionAcl === AclEdit) && this.props.removeDocument) {
+            moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+        }
 
         !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
         cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
@@ -991,7 +993,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @computed get ignorePointerEvents() {
         return this.props.pointerEvents === false ||
-            (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
+            (this.Document._isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
             (this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None);
     }
     @undoBatch
@@ -1013,11 +1015,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     });
 
     renderLock() {
-        return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
+        return (this.Document._isBackground !== undefined || this.isSelected(false)) &&
             ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
             this.props.renderDepth > 0 && !this.props.treeViewDoc ?
             <div className="documentView-lock" onClick={this.toggleBackground}>
-                <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+                <FontAwesomeIcon icon={this.Document._isBackground ? "unlock" : "lock"} style={{ color: this.Document._isBackground ? "red" : undefined }} size="lg" />
             </div>
             : (null);
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 855160524..6cce5fb87 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -145,7 +145,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         if (field) {
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
-            funcs.push({ description: "Make Background", event: () => { this.layoutDoc.isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" });
+            funcs.push({ description: "Make Background", event: () => { this.layoutDoc._isBackground = true; this.props.bringToFront?.(this.rootDoc); }, 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" });
@@ -452,7 +452,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
                 height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
-                pointerEvents: this.layoutDoc.isBackground ? "none" : undefined,
+                pointerEvents: this.layoutDoc._isBackground ? "none" : undefined,
                 borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
             }} >
             <CollectionFreeFormView {...this.props}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 50b2af0d7..1ef25c80d 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -92,7 +92,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
     openLinkTargetOnRight = (e: React.MouseEvent) => {
         const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
         alias.isLinkButton = undefined;
-        alias.isBackground = undefined;
+        alias._isBackground = undefined;
         alias.layoutKey = "layout";
         this.props.addDocTab(alias, "onRight");
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 02c62be95..f60c521ad 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -262,10 +262,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
             const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
             bestTarget && runInAction(() => {
-                bestTarget!._viewTransition = "all 1s";
-                bestTarget!._panX = activeItem.presPinViewX;
-                bestTarget!._panY = activeItem.presPinViewY;
-                bestTarget!._viewScale = activeItem.presPinViewScale;
+                bestTarget._viewTransition = "all 1s";
+                bestTarget._panX = activeItem.presPinViewX;
+                bestTarget._panY = activeItem.presPinViewY;
+                bestTarget._viewScale = activeItem.presPinViewScale;
             });
             //setTimeout(() => targetDoc._viewTransition = undefined, 1010);
         }
@@ -548,7 +548,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     removeDocument = (doc: Doc) => { Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); };
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
-    active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) &&
+    active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground) &&
         (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
     /**
@@ -674,7 +674,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             }
             handled = true;
         }
-    })
+    });
 
     /**
      * 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 88c3e6730..85d010aaa 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -185,8 +185,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         this._videoRef = vref;
         if (vref) {
             this._videoRef!.ontimeupdate = this.updateTimecode;
+            // @ts-ignore
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
-            this._reactionDisposer && this._reactionDisposer();
+            this._reactionDisposer?.();
             this._reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0),
                 time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
         }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f0e3a2b54..7758b1815 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -476,7 +476,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 {view}
             </div>;
             {!frozen ? (null) :
-                <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
+                <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc._isBackground ? undefined : "all" }}
                     onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
                     <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
                         <div className="indicator" ref={this._iframeIndicatorRef}></div>
@@ -682,7 +682,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     transform: `scale(${this.props.ContentScaling()})`,
                     width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
                     height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
-                    pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+                    pointerEvents: this.layoutDoc._isBackground ? "none" : undefined
                 }}
                 onContextMenu={this.specificContextMenu}>
                 <base target="_blank" />
@@ -690,7 +690,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 <div className={"webBox-outerContent"} ref={this._outerRef}
                     style={{
                         width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%",
-                        pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none"
+                        pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc._isBackground ? "all" : "none"
                     }}
                     onWheel={e => e.stopPropagation()}
                     onPointerDown={this.onMarqueeDown}
@@ -709,7 +709,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     }}>
                     <div className={"webBox-innerContent"} style={{
                         height: NumCast(this.layoutDoc.scrollHeight),
-                        pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+                        pointerEvents: this.layoutDoc._isBackground ? "none" : undefined
                     }}>
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 063cdb0cc..356e36b76 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -94,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _linkTime: number | null = null;
     private _pause: boolean = false;
 
-    @computed get _recording() { return this.dataDoc.audioState === "recording"; }
+    @computed get _recording() { return this.dataDoc?.audioState === "recording"; }
     set _recording(value) {
         this.dataDoc.audioState = value ? "recording" : undefined;
     }
@@ -818,6 +818,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         );
         this._disposers.editorState = reaction(
             () => {
+                if (!this.dataDoc || !this.layoutDoc) return undefined;
                 if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
                     return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
                 }
@@ -853,13 +854,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         );
         this._disposers.autoHeight = reaction(
-            () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
-            () => setTimeout(() => this.tryUpdateHeight(), 0)
+            () => ({
+                width: NumCast(this.layoutDoc._width),
+                autoHeight: this.layoutDoc?._autoHeight
+            }),
+            ({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0)
         );
         this._disposers.height = reaction(
-            () => this.layoutDoc[HeightSym](),
+            () => NumCast(this.layoutDoc._height),
             action(height => {
-                if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
+                if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
                     this.layoutDoc._delayAutoHeight = height;
                 }
             })
@@ -1490,7 +1494,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         TraceMobx();
         const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
+        const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground;
         this.props.isSelected() && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props), 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
         if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 7ff21975b..c33637d0f 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -214,7 +214,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
             PresBox.Instance._selectedArray.splice(PresBox.Instance._selectedArray.indexOf(this.rootDoc), 1);
         }
         e.stopPropagation();
-    })
+    });
 
     render() {
         const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : "");
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 7e233ecbb..a24761195 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -507,7 +507,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                     </div>
                     <div className="searchBox-lozenge-dashboard"  >
                         <select className="searchBox-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
-                            value={myDashboards.indexOf(Cast(Doc.UserDoc().activeDashboard, Doc, null)!)}>
+                            value={myDashboards.indexOf(Cast(Doc.UserDoc().activeDashboard, Doc, null))}>
                             {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
                         </select>
                         <div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e569dabc1..cb4ca9a25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -939,16 +939,19 @@ export namespace Doc {
     }
 
     export function IsBrushed(doc: Doc) {
+        return false;
         return computedFn(function IsBrushed(doc: Doc) {
             return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
         })(doc);
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
+        return 0;
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
+        return 0;
         return computedFn(function IsBrushDegree(doc: Doc) {
             return Doc.IsBrushedDegreeUnmemoized(doc);
         })(doc);
@@ -977,6 +980,7 @@ export namespace Doc {
 
     let _lastDate = 0;
     export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
+        return;
         linkFollowUnhighlight();
         Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
@@ -1128,6 +1132,15 @@ export namespace Doc {
         return false;
     }
 
+    export function copyDragFactory(dragFactory: Doc) {
+        const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+            Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
+        }
+        return ndoc;
+    }
+
 
     export namespace Get {
 
@@ -1276,14 +1289,7 @@ Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
 Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) {
-    const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
-    if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
-        dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
-        Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
-    }
-    return ndoc;
-});
+Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
 Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
 Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index a83fec4ab..76b26a9aa 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -101,7 +101,7 @@ export const documentSchema = createSchema({
     linkDisplay: "boolean",     // whether a link connection should be shown between link anchor endpoints.
     isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
     isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
-    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
+    _isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     _lockedTransform: "boolean",// whether a freeformview can pan/zoom
 
-- 
cgit v1.2.3-70-g09d2


From 4f3c8cfab12f1c5fff27faa0998a286d210148d2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 16:39:17 -0400
Subject: major restructure of how addDocTabs works.  now you can add/replace
 with options for left/right/top/bottom and specific panel names

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DictationManager.ts                |   4 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/animationtimeline/Keyframe.tsx    |   2 +-
 .../views/collections/CollectionDockingView.tsx    | 298 ++++++++-------------
 .../views/collections/CollectionSchemaCells.tsx    |   2 +-
 .../CollectionSchemaMovableTableHOC.tsx            |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 src/client/views/collections/CollectionView.tsx    |   8 +-
 src/client/views/collections/SchemaTable.tsx       |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   4 +-
 src/client/views/linking/LinkEditor.tsx            |  20 +-
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  16 +-
 src/client/views/nodes/FontIconBox.tsx             |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |   2 +-
 src/client/views/nodes/KeyValuePair.tsx            |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   6 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   6 +-
 src/client/views/nodes/PresBox.tsx                 |   6 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  12 +-
 .../formattedText/FormattedTextBoxComment.tsx      |  16 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |   4 +-
 src/client/views/pdf/Annotation.tsx                |   2 +-
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/fields/RichTextUtils.ts                        |   2 +-
 39 files changed, 202 insertions(+), 268 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ca1b95d7..262086735 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -847,7 +847,7 @@ export namespace Docs {
                     {
                         type: type,
                         content: [
-                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, false, config.initialWidth))
+                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))
                         ]
                     }
                 ]
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7937072da..20f8fe9c1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1063,7 +1063,7 @@ export class CurrentUserUtils {
 Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
     const copy = Doc.copyDragFactory(dragFactory);
     if (copy) {
-        CollectionDockingView.AddRightSplit(copy);
+        CollectionDockingView.AddSplit(copy, "right");
         const view = DocumentManager.Instance.getFirstDocumentView(copy);
         view && SelectionManager.SelectDoc(view, false);
     }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index e66c15fcb..231e1fa8d 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -323,7 +323,7 @@ export namespace DictationManager {
             ["open fields", {
                 action: (target: DocumentView) => {
                     const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
-                    target.props.addDocTab(kvp, "onRight");
+                    target.props.addDocTab(kvp, "add:right");
                 }
             }],
 
@@ -337,7 +337,7 @@ export namespace DictationManager {
                     const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
                     proto.data = new RichTextField(proseMirrorState);
                     proto.backgroundColor = "#eeffff";
-                    target.props.addDocTab(newBox, "onRight");
+                    target.props.addDocTab(newBox, "add:right");
                 }
             }]
 
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb54fbefc..93c37dbd1 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -124,7 +124,7 @@ export class DocumentManager {
     }
 
     static addRightSplit = (doc: Doc, finished?: () => void) => {
-        CollectionDockingView.AddRightSplit(doc);
+        CollectionDockingView.AddSplit(doc, "right");
         finished?.();
     }
     public jumpToDocument = async (
@@ -231,7 +231,7 @@ export class DocumentManager {
                     containerDoc._currentTimecode = targetTimecode;
                     const targetContext = await target?.context as Doc;
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
                 } else {
                     finished?.();
                 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index dd223390a..764a2547c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -156,7 +156,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                             dataDoc.googleDoc = googleDoc;
                         }
-                        CollectionDockingView.AddRightSplit(googleDoc);
+                        CollectionDockingView.AddSplit(googleDoc, "right");
                     } else if (e.altKey) {
                         e.preventDefault();
                         window.open(googleDocUrl);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6c64a3714..c7a24133c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -188,7 +188,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                 alias.context = undefined;
                 //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
-                CollectionDockingView.AddRightSplit(alias);
+                CollectionDockingView.AddSplit(alias, "right");
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2794a9f4e..db3e3d0dd 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -210,7 +210,7 @@ export class KeyManager {
                         return { stopPropagation: false, preventDefault: false };
                     }
                 }
-                MainView.Instance.mainFreeform && CollectionDockingView.AddRightSplit(MainView.Instance.mainFreeform);
+                MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, "right");
                 break;
             case "arrowleft":
                 if (document.activeElement) {
@@ -218,7 +218,7 @@ export class KeyManager {
                         return { stopPropagation: false, preventDefault: false };
                     }
                 }
-                MainView.Instance.mainFreeform && CollectionDockingView.CloseRightSplit(MainView.Instance.mainFreeform);
+                MainView.Instance.mainFreeform && CollectionDockingView.CloseSplit(MainView.Instance.mainFreeform);
                 break;
             case "backspace":
                 if (document.activeElement) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6a960f4d5..7b0c6fa9f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -236,7 +236,7 @@ export class MainView extends React.Component {
                 reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
                     initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
                         if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
-                            CollectionDockingView.AddRightSplit(docField);
+                            CollectionDockingView.AddSplit(docField, "right");
                         }
                     }),
                 );
@@ -260,7 +260,7 @@ export class MainView extends React.Component {
         }
         const pres = Docs.Create.PresDocument(new List<Doc>(),
             { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true });
-        CollectionDockingView.AddRightSplit(pres);
+        CollectionDockingView.AddSplit(pres, "right");
         Doc.UserDoc().activePresentation = pres;
         const myPresentations = Doc.UserDoc().myPresentations as Doc;
         Doc.AddDocToList(myPresentations, "data", pres);
@@ -382,9 +382,9 @@ export class MainView extends React.Component {
     @computed get topOffset() { return (CollectionMenu.Instance?.Pinned ? 35 : 0) + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); }
     flyoutWidthFunc = () => this.flyoutWidth;
     addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
-        return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
+        return where === "close" ? CollectionDockingView.CloseSplit(doc) :
             doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) :
-                CollectionDockingView.AddRightSplit(doc);
+                CollectionDockingView.AddSplit(doc, "right");
     }
     sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index e95b28d6c..6ac6571d3 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -160,7 +160,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                                 googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                                 dataDoc.googleDoc = googleDoc;
                             }
-                            CollectionDockingView.AddRightSplit(googleDoc);
+                            CollectionDockingView.AddRight(googleDoc, "right");
                         } else if (e.altKey) {
                             e.preventDefault();
                             window.open(googleDocUrl);
@@ -343,7 +343,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 dv.toggleFollowLink("inPlace", true, false);
             } else if (value === "linkOnRight") {
                 dv.noOnClick();
-                dv.toggleFollowLink("onRight", false, false);
+                dv.toggleFollowLink("add:right", false, false);
             }
         });
     }
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index b382298f3..427748fe7 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -44,7 +44,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panX = NumCast(target.x) + NumCast(target._width) / 2;
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
-        this.props.addDocTab(col, "inTab");
+        this.props.addDocTab(col, "add:right");
     }
 
     render() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index fef0a3e00..d9ab5d029 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -258,7 +258,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get contexts() {
-        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
+        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
     }
 
     previewBackground = () => "lightgrey";
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 4fb362ab1..e84022366 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -331,7 +331,7 @@ export class Keyframe extends React.Component<IProps> {
         }),
             TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
                 const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
-                CollectionDockingView.AddRightSplit(kvp);
+                CollectionDockingView.AddSplit(kvp, "right");
             })),
             TimelineMenu.Instance.addItem("button", "Delete", action(() => {
                 (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8301d3df8..62c865436 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,6 +1,6 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { clamp } from 'lodash';
+import { clamp, pull } from 'lodash';
 import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
@@ -38,7 +38,7 @@ const _global = (window /* browser */ || global /* node */) as any;
 @observer
 export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @observable public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc, isDisplayPanel?: boolean, width?: number) {
+    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -46,7 +46,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             width: width,
             props: {
                 documentId: document[Id],
-                isDisplayPanel   // flag for whether a tab should be considered a placeholder that has its contents replaced with new content
+                panelName   // name of tab that can be used to close or replace its contents
             }
         };
     }
@@ -85,26 +85,20 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     @undoBatch
-    @action
-    public static CloseRightSplit(document: Opt<Doc>): boolean {
-        const tryClose = (childItem: any) => {
-            if (childItem.config?.component === "DocumentFrameRenderer") {
-                if ((!document && childItem.config.props.isDisplayPanel) || (document && childItem.config.props.documentId === document[Id])) {
-                    childItem.remove();
-                    return true;
-                }
+    public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
+        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document);
+        if (tab) {
+            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+            if (j !== -1) {
+                tab.header.parent.contentItems[j].remove();
+                return CollectionDockingView.Instance.layoutChanged();
             }
-            return false;
-        };
-        const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
+        }
 
-        retVal && document && CollectionDockingView.Instance.layoutChanged();
-        return retVal;
+        return false;
     }
 
     @undoBatch
-    @action
     public static OpenFullScreen(doc: Doc, libraryPath?: Doc[]) {
         const instance = CollectionDockingView.Instance;
         if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
@@ -124,176 +118,94 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
-        if (!CollectionDockingView.Instance) return false;
-        const instance = CollectionDockingView.Instance;
-        const retVal = !instance._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
-                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    child.contentItems[0].config.isDisplayPanel) {
-                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
-                    child.addChild(newItemStackConfig, undefined);
-                    !addToSplit && child.contentItems[0].remove();
-                    return true;
-                }
-                return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
-                    if (tab.config.props.isDisplayPanel) {
-                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
-                        child.addChild(newItemStackConfig, undefined);
-                        !addToSplit && child.contentItems[j].remove();
-                        return true;
-                    }
-                    return false;
-                });
-            });
-        retVal && instance.layoutChanged();
-        return retVal;
-    }
-
-    //
-    //  Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
-    //
     @undoBatch
-    @action
-    public static AddRightSplit(document: Doc, isDisplayPanel: Opt<boolean> = undefined) {
-        if (!CollectionDockingView.Instance) return false;
-
-        const ind = Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === document);
-        if (ind !== -1) {
-            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys())[ind];
-            const activeContentItem = tab.header.parent.getActiveContentItem();
-            if (tab.contentItem !== activeContentItem) {
-                tab.header.parent.setActiveContentItem(tab.contentItem);
-            }
-            tab.setActive(true);
-        } else {
-            if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
-            const instance = CollectionDockingView.Instance;
-            const newItemStackConfig = {
-                type: 'stack',
-                content: [CollectionDockingView.makeDocumentConfig(document, isDisplayPanel)]
-            };
-
-            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-            if (instance._goldenLayout.root.contentItems.length === 0) {
-                instance._goldenLayout.root.addChild(newContentItem);
-            } else if (instance._goldenLayout.root.contentItems[0].isRow) {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else {
-                const collayout = instance._goldenLayout.root.contentItems[0];
-                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-                collayout.parent.replaceChild(collayout, newRow);
-
-                newRow.addChild(newContentItem, undefined, true);
-                newRow.addChild(collayout, 0, true);
-
-                collayout.config.width = 50;
-                newContentItem.config.width = 50;
-            }
-            newContentItem.callDownwards('_$init');
-            instance.layoutChanged();
+    public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean {
+        const instance = CollectionDockingView.Instance;
+        if (!instance) return false;
+        const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+        if (!panelName && stack) {
+            const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
+            const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
+            stack.addChild(newContentItem.contentItems[0], undefined);
+            stack.contentItems[activeContentItemIndex].remove();
+            return CollectionDockingView.Instance.layoutChanged();
         }
-        return true;
+        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => tab.contentItem.config.props.panelName === panelName);
+        if (tab) {
+            tab.header.parent.addChild(newConfig, undefined);
+            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+            !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
+            return CollectionDockingView.Instance.layoutChanged();
+        }
+        return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
 
-
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
-    @action
-    public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
-        if (!CollectionDockingView.Instance) return false;
+    @undoBatch
+    public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
         const instance = CollectionDockingView.Instance;
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined)]
-        };
+        if (!instance) return false;
+        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
 
-        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-        if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
-            instance._goldenLayout.root.addChild(newContentItem);
-        } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
-            if (pullSide === "left") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
-            } else if (pullSide === "right") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else if (pullSide === "top" || pullSide === "bottom") {
-                // if not going in a row layout, must add already existing content into column
-                const rowlayout = instance._goldenLayout.root.contentItems[0];
-                const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
-                rowlayout.parent.replaceChild(rowlayout, newColumn);
-                if (pullSide === "top") {
-                    newColumn.addChild(rowlayout, undefined, true);
-                    newColumn.addChild(newContentItem, 0, true);
-                } else if (pullSide === "bottom") {
-                    newColumn.addChild(newContentItem, undefined, true);
-                    newColumn.addChild(rowlayout, 0, true);
+        if (!pullSide && stack) {
+            stack.addChild(docContentConfig, undefined);
+        } else {
+            const newItemStackConfig = { type: 'stack', content: [docContentConfig] };
+            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+            if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
+                instance._goldenLayout.root.addChild(newContentItem);
+            } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+                switch (pullSide) {
+                    default:
+                    case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+                    case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+                    case "top":
+                    case "bottom":
+                        // if not going in a row layout, must add already existing content into column
+                        const rowlayout = instance._goldenLayout.root.contentItems[0];
+                        const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+                        rowlayout.parent.replaceChild(rowlayout, newColumn);
+                        if (pullSide === "top") {
+                            newColumn.addChild(rowlayout, undefined, true);
+                            newColumn.addChild(newContentItem, 0, true);
+                        } else if (pullSide === "bottom") {
+                            newColumn.addChild(newContentItem, undefined, true);
+                            newColumn.addChild(rowlayout, 0, true);
+                        }
+
+                        rowlayout.config.height = 50;
+                        newContentItem.config.height = 50;
                 }
-
-                rowlayout.config.height = 50;
-                newContentItem.config.height = 50;
-            }
-        } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
-            if (pullSide === "top") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
-            } else if (pullSide === "bottom") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else if (pullSide === "left" || pullSide === "right") {
-                // if not going in a row layout, must add already existing content into column
-                const collayout = instance._goldenLayout.root.contentItems[0];
-                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-                collayout.parent.replaceChild(collayout, newRow);
-
-                if (pullSide === "left") {
-                    newRow.addChild(collayout, undefined, true);
-                    newRow.addChild(newContentItem, 0, true);
-                } else if (pullSide === "right") {
-                    newRow.addChild(newContentItem, undefined, true);
-                    newRow.addChild(collayout, 0, true);
+            } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+                switch (pullSide) {
+                    case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+                    case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+                    case "left":
+                    case "right":
+                    default:
+                        // if not going in a row layout, must add already existing content into column
+                        const collayout = instance._goldenLayout.root.contentItems[0];
+                        const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                        collayout.parent.replaceChild(collayout, newRow);
+
+                        if (pullSide === "left") {
+                            newRow.addChild(collayout, undefined, true);
+                            newRow.addChild(newContentItem, 0, true);
+                        } else {
+                            newRow.addChild(newContentItem, undefined, true);
+                            newRow.addChild(collayout, 0, true);
+                        }
+
+                        collayout.config.width = 50;
+                        newContentItem.config.width = 50;
                 }
-
-                collayout.config.width = 50;
-                newContentItem.config.width = 50;
             }
+            newContentItem.callDownwards('_$init');
         }
 
-        newContentItem.callDownwards('_$init');
-        instance.layoutChanged();
-        return true;
-    }
-
-    //
-    //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
-    //
-    @undoBatch
-    public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
-        if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            return CollectionDockingView.AddRightSplit(document, true);
-        }
-        return false;
-    }
-
-    @undoBatch
-    public static AddTab(stack: any, document: Doc, libraryPath?: Doc[]) {
-        const instance = CollectionDockingView.Instance;
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
-        if (stack === undefined) {
-            stack = instance._goldenLayout.root;
-            while (!stack.isStack) {
-                if (stack.contentItems.length) {
-                    stack = stack.contentItems[0];
-                } else {
-                    stack.addChild({ type: 'stack', content: [docContentConfig] });
-                    stack = undefined;
-                    break;
-                }
-            }
-        }
-        stack?.addChild(docContentConfig, undefined);
-        instance.layoutChanged();
-        return true;
+        return instance.layoutChanged();
     }
 
     @undoBatch
@@ -303,6 +215,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         this._goldenLayout.emit('stateChanged');
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
+        return true;
     }
 
     async setupGoldenLayout() {
@@ -450,7 +363,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
     tabCreated = (tab: any) => {
-        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
+        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
     }
 
     stackCreated = (stack: any) => {
@@ -458,9 +371,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             if (e.target === stack.header.element[0] && e.button === 2) {
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }));
+                }), "", stack);
             }
         });
 
@@ -477,9 +390,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }));
+                }), "", stack);
             }));
     }
 
@@ -601,7 +514,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 Doc.AddDocToList(curPres, "data", pinDoc);
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                    CollectionDockingView.AddRightSplit(curPres);
+                    CollectionDockingView.AddSplit(curPres, "right");
                 }
                 DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
             }
@@ -688,17 +601,26 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
     get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
 
+    // adds a tab to the layout based on the locaiton parameter which can be:
+    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
+    //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
+    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, 
+    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, 
+    //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
+    //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
         if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
-        switch (location) {
-            case "onRight": return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
-                CollectionDockingView.CloseRightSplit(doc) : CollectionDockingView.AddRightSplit(doc);
-            case "close": return CollectionDockingView.CloseRightSplit(doc);
-            case "replace": return CollectionDockingView.UseRightSplit(doc);
+        const locationFields = location.split(":");
+        const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+        switch (locationFields[0]) {
+            case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+            case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
             case "inPlace":
-            default: return CollectionDockingView.AddTab(this.stack, doc);
+            case "add":
+            default: return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
+                CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, locationParams, this.stack);
         }
     }
 
@@ -830,6 +752,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div >);
     }
 }
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
     "opens up the inputted document on the right side of the screen", "(doc: any)");
-Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 18ca29aaa..0d3a951cf 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -370,7 +370,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
                         })}
                     />
                 </div >
-                <div onClick={() => this._doc && this.props.addDocTab(this._doc, "onRight")} className="collectionSchemaView-cellContents-docButton">
+                <div onClick={() => this._doc && this.props.addDocTab(this._doc, "add:right")} className="collectionSchemaView-cellContents-docButton">
                     <FontAwesomeIcon icon="external-link-alt" size="lg" />
                 </div>
             </div>;
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index a3475b481..383a9312f 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -246,7 +246,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
                         <div className="row-dragger">
                             <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
                             <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
-                            <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+                            <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
                         </div>
                         {children}
                     </ReactTableDefaults.TrComponent>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fffbe65a3..80a6afba7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -117,7 +117,7 @@ class TreeView extends React.Component<TreeViewProps> {
             Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
     }
 
-    @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight");
+    @undoBatch openRight = () => this.props.addDocTab(this.doc, "add:right");
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1da0e601a..64ad3abbf 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -305,7 +305,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             this.setupViewTypes("UI Controls...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
-                this.props.addDocTab(newRendition, "onRight");
+                this.props.addDocTab(newRendition, "add:right");
                 return newRendition;
             }, false);
 
@@ -313,10 +313,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             const optionItems = options && "subitems" in options ? options.subitems : [];
             !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null;
             if (this.props.Document.childLayout instanceof Doc) {
-                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "add:right"), icon: "project-diagram" });
             }
             if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
-                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
             }
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
@@ -331,7 +331,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
                     const alias = Doc.MakeAlias(this.props.Document);
                     DocUtils.makeCustomViewClicked(alias, undefined, func.key);
-                    this.props.addDocTab(alias, "onRight");
+                    this.props.addDocTab(alias, "add:right");
                 }
             }));
             DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 2fe00a245..1fb7aa04a 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -545,7 +545,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     onOpenClick = () => {
-        this._showDoc && this.props.addDocTab(this._showDoc, "onRight");
+        this._showDoc && this.props.addDocTab(this._showDoc, "add:right");
     }
 
     getPreviewTransform = (): Transform => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 759d142ea..1aa30fc02 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -76,7 +76,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
         if (e.key === "?") {
             cm.setDefaultItem("?", (str: string) => this.props.addDocTab(
-                Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "onRight"));
+                Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "add:right"));
 
             cm.displayMenu(this._downX, this._downY);
             e.stopPropagation();
@@ -400,7 +400,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             Doc.AddDocToList(curPres, "data", pinDoc);
             if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
             if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                CollectionDockingView.AddRightSplit(curPres);
+                CollectionDockingView.AddSplit(curPres, "right");
             }
             if (e instanceof KeyboardEvent ? e.key === "c" : true) {
                 const x = this.Bounds.left + this.Bounds.width / 2;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 8e391f995..c9c2e657b 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -376,21 +376,33 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         Default
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("onRight")}>
-                        Always open in a new pane
+                        onPointerDown={() => this.changeFollowBehavior("add:left")}>
+                        Always open in new left pane
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("replace")}>
+                        onPointerDown={() => this.changeFollowBehavior("add:right")}>
+                        Always open in new right pane
+                        </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace:right")}>
                         Always replace right tab
                         </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace:left")}>
+                        Always replace left tab
+                        </div>
                     <div className="linkEditor-followingDropdown-option"
                         onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
                         Always open full screen
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("inTab")}>
+                        onPointerDown={() => this.changeFollowBehavior("add")}>
                         Always open in a new tab
                         </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace")}>
+                        Replace Tab
+                        </div>
                     {this.props.linkDoc.linksToAnnotation ?
                         <div className="linkEditor-followingDropdown-option"
                             onPointerDown={() => this.changeFollowBehavior("openExternal")}>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a77122456..05b6b584d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -167,7 +167,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 });
             }
         } else {
-            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "add:right"), false);
         }
 
         linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId), this.props.destinationDoc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2d03c5154..48a1688a6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -180,10 +180,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
         RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
 
-        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
         const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
         (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
-        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
 
@@ -323,7 +323,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                                     fullScreenDoc = Doc.MakeAlias(this.props.Document);
                                     fullScreenDoc.layoutKey = "layout_fullScreen";
                                 }
-                                this.props.addDocTab(fullScreenDoc, "inTab");
+                                this.props.addDocTab(fullScreenDoc, "add");
                             }, "double tap");
                             SelectionManager.DeselectAll();
                         }
@@ -351,7 +351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 500);
                 } else clickFunc();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
-                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
+                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
             } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
@@ -748,7 +748,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         const appearance = cm.findByDescription("UI Controls...");
         const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
-        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
+        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
         //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
         !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
 
@@ -766,7 +766,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
             onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
             onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
-            !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "link" });
+            !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
             onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
             onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
             onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
@@ -810,8 +810,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const help = cm.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
-        !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+        !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
         helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index bc5abb0a4..87142babd 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -39,7 +39,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
 
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
-        dragFactory && this.props.addDocTab(dragFactory, "onRight");
+        dragFactory && this.props.addDocTab(dragFactory, "add:right");
     }
     dragAsTemplate = (): void => {
         this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b732f5f83..c5ff42a1a 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -230,7 +230,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         openItems.push({
             description: "Default Perspective", event: () => {
                 this.props.addDocTab(this.props.Document, "close");
-                this.props.addDocTab(this.fieldDocToLayout, "onRight");
+                this.props.addDocTab(this.fieldDocToLayout, "add:right");
             }, icon: "image"
         });
         !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 6dd85b7f5..74d10d087 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8e5ee09ff..10b6aa02e 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -73,7 +73,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false);
+                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "add:right")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
             }
@@ -87,14 +87,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
     }
 
     openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.rootDoc, "onRight");
+        this.props.addDocTab(this.rootDoc, "add:right");
     }
     openLinkTargetOnRight = (e: React.MouseEvent) => {
         const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
         alias.isLinkButton = undefined;
         alias._isBackground = undefined;
         alias.layoutKey = "layout";
-        this.props.addDocTab(alias, "onRight");
+        this.props.addDocTab(alias, "add:right");
     }
     @action
     openLinkEditor = action((e: React.MouseEvent) => {
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index f154c5f56..e174a95af 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -40,7 +40,7 @@ export class LinkDocPreview extends React.Component<Props> {
     async followDefault() {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
-        this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null;
+        this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "add:right"), false) : null;
     }
 
     componentDidUpdate() { this.updatePreview(); }
@@ -69,9 +69,9 @@ export class LinkDocPreview extends React.Component<Props> {
     pointerDown = (e: React.PointerEvent) => {
         if (this.props.linkDoc && this.props.linkSrc) {
             DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
-                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
         } else if (this.props.href) {
-            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
+            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "add:right");
         }
     }
     width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f60c521ad..a03c45a53 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -238,7 +238,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
 
         // If openDocument is selected then it should open the document for the user
         if (activeItem.openDocument) {
-            this.props.addDocTab(activeItem, "replace");
+            this.props.addDocTab(activeItem, "replace:right");
         } else
             //docToJump stayed same meaning, it was not in the group or was the last element in the group
             if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') {
@@ -443,7 +443,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         if (this.layoutDoc.inOverlay) {
             this.layoutDoc.presStatus = 'edit';
             Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
-            CollectionDockingView.AddRightSplit(this.rootDoc);
+            CollectionDockingView.AddSplit(this.rootDoc, "right");
             this.layoutDoc.inOverlay = false;
         } else if (this.layoutDoc.context && docView) {
             this.layoutDoc.presStatus = 'manual';
@@ -1115,7 +1115,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 DockedFrameRenderer.PinDoc(doc, false);
                 this.gotoDocument(this.childDocs.length, this.itemIndex);
             } else {
-                this.props.addDocTab(doc, "onRight");
+                this.props.addDocTab(doc, "add:right");
             }
         }
     }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 578061344..d50a10bdd 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -82,7 +82,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     @action public FullScreen() {
         this._fullScreen = true;
         this.player && this.player.requestFullscreen();
-        this._youtubePlayer && this.props.addDocTab(this.rootDoc, "inTab");
+        this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
     }
 
     choosePath(url: string) {
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 924079096..f2658e77e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -169,7 +169,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     onPointerDownEnumerables = async (e: any) => {
         e.stopPropagation();
         const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
-        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
+        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right");
     }
 
 
@@ -191,7 +191,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
             list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
             alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
-            this.props.tbox.props.addDocTab(alias, "onRight");
+            this.props.tbox.props.addDocTab(alias, "add:right");
         }
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 356e36b76..c49e38f72 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -222,7 +222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
                     const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
-                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value });
+                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -465,7 +465,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const linkDoc = data.linkDocument!;
         const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
         const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
-        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
+        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "add:right", anchor1Id);
     }
 
     getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -811,7 +811,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (linkDoc) {
                     const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
                     const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
-                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id);
+                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
                 }
             },
             { fireImmediately: true }
@@ -1096,7 +1096,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
             const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
-            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true });
+            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
             marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
             return node.mark(marks);
         }
@@ -1243,10 +1243,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         FormattedTextBoxComment.Hide();
         if (FormattedTextBoxComment.linkDoc) {
             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
             } else {
                 DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
-                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 55d225adc..f41f03620 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -103,7 +103,7 @@ export class FormattedTextBoxComment {
                             this.deleteLink();
                         } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
                             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
                             } else {
                                 const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ?
                                     Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc))
@@ -112,28 +112,28 @@ export class FormattedTextBoxComment {
 
                                 if (FormattedTextBoxComment.linkDoc.follow) {
                                     if (FormattedTextBoxComment.linkDoc.follow === "Default") {
-                                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+                                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
                                     } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") {
-                                        if (target) { textBox.props.addDocTab(target, "onRight"); }
+                                        if (target) { textBox.props.addDocTab(target, "add:right"); }
                                     } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") {
-                                        if (target) { textBox.props.addDocTab(target, "inTab"); }
+                                        if (target) { textBox.props.addDocTab(target, "add"); }
                                     }
                                 } else {
-                                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+                                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
                                 }
                             }
                         } else {
                             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
                             } else {
                                 DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
                             }
                         }
 
                     }
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
-                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
+                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "add:right");
                 }
                 keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index a65eea50f..c6bacc1a8 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -104,7 +104,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //Command to create a new Tab with a PDF of all the command shortcuts
     bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 });
-        props.addDocTab(newDoc, "onRight");
+        props.addDocTab(newDoc, "add:right");
     });
 
     //Commands to modify BlockType
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 399f72608..307238ea1 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -810,7 +810,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
             <div className="dropdown link-menu">
                 <p>Linked to:</p>
                 <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
                 <div className="divider"></div>
                 <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
             </div>;
@@ -857,7 +857,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     // TODO: should check for valid URL
     @undoBatch
     makeLinkToURL = (target: string, lcoation: string) => {
-        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
+        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 7e632a0ee..3fadfe842 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -279,7 +279,7 @@ export class RichTextRules {
                                 DocUtils.Publish(target, docid, returnFalse, returnFalse);
                                 DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
                             });
-                            const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+                            const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "add:right", title: docid, targetId: docid });
                             return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
                         }
                         return state.tr;
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1616500f6..64f7d27e5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -148,7 +148,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             alt: { default: null },
             title: { default: null },
             float: { default: "left" },
-            location: { default: "onRight" },
+            location: { default: "add:right" },
             docid: { default: "" }
         },
         group: "inline",
@@ -177,7 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             height: { default: 100 },
             title: { default: null },
             float: { default: "right" },
-            location: { default: "onRight" },
+            location: { default: "add:right" },
             hidden: { default: false },
             fieldKey: { default: "" },
             docid: { default: "" },
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 98638ecc2..a33068a18 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -99,7 +99,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
+                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ed33ac822..d89e66ec5 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -604,7 +604,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
                         const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true;
-                        if (link) link.followLinkLocation = "onRight";
+                        if (link) link.followLinkLocation = "add:right";
                     }
                 }
             });
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a590c88c4..0b5f14d74 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -278,7 +278,7 @@ export namespace RichTextUtils {
             } else {
                 docid = backingDocId;
             }
-            return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" });
+            return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
-- 
cgit v1.2.3-70-g09d2


From 6f72743516e47a6ad077bb8e894c8005fee29fc7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 2 Sep 2020 00:12:59 -0400
Subject: fixed search in text boxes to ignore case.  fixed creating new tabs
 when only a stack exists.  fixed SearchBox to only create one time.

---
 src/client/views/MainView.tsx                             | 10 ++++++++--
 src/client/views/collections/CollectionDockingView.tsx    | 10 +++++-----
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                         |  5 +++--
 4 files changed, 17 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6db518f1e..7c7c5b72b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -56,6 +56,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
 import { TraceMobx } from '../../fields/util';
+import { SelectionManager } from '../util/SelectionManager';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -519,12 +520,18 @@ export class MainView extends React.Component {
             </defs>
         </svg>;
     }
+    select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     @computed get search() {
         TraceMobx();
         return <div className="mainView-searchPanel">
-            <DocumentView Document={CurrentUserUtils.MySearchPanelDoc}
+            <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
                 DataDoc={undefined}
+                fieldKey="data"
+                dropAction="move"
+                isSelected={returnTrue}
+                active={returnTrue}
+                select={this.select}
                 LibraryPath={emptyPath}
                 addDocument={undefined}
                 addDocTab={this.addDocTabFunc}
@@ -541,7 +548,6 @@ export class MainView extends React.Component {
                 PanelHeight={this.getPHeight}
                 renderDepth={0}
                 focus={emptyFunction}
-                parentActive={returnTrue}
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
                 docFilters={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index d39ef5e80..a3d58f31d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -173,7 +173,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                         rowlayout.config.height = 50;
                         newContentItem.config.height = 50;
                 }
-            } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+            } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
                 switch (pullSide) {
                     case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
                     case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
@@ -360,8 +360,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     stackCreated = (stack: any) => {
-        stack.header.element.on('mousedown', (e: any) => {
-            if (e.target === stack.header.element[0] && e.button === 2) {
+        stack.header?.element.on('mousedown', (e: any) => {
+            if (e.target === stack.header?.element[0] && e.button === 2) {
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
@@ -370,14 +370,14 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             }
         });
 
-        stack.header.controlsContainer.find('.lm_close') //get the close icon
+        stack.header?.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(() => {
                 //if (confirm('really close this?')) {
                 stack.remove();
                 stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true));
             }));
-        stack.header.controlsContainer.find('.lm_popout') //get the close icon
+        stack.header?.controlsContainer.find('.lm_popout') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(() => {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c49e38f72..436538eba 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -499,7 +499,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (node.isTextblock) {
             let index = 0, foundAt;
             const ep = this.getNodeEndpoints(pm.state.doc, node);
-            const regexp = find.replace("*", "");
+            const regexp = new RegExp(find.replace("*", ""), "i");
             if (regexp) {
                 while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
                     const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a4fcfce5e..bc31c1a21 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -922,8 +922,9 @@ export namespace Doc {
             brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
     }
     export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        brushManager.SearchMatchDoc.set(doc, results);
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+            brushManager.SearchMatchDoc.set(doc, results);
+        }
         return doc;
     }
     export function SearchMatchNext(doc: Doc, backward: boolean) {
-- 
cgit v1.2.3-70-g09d2


From 3ba4d0b56b5a0428d1b363ee05b419b954a2b071 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 3 Sep 2020 15:59:51 -0400
Subject: fixed linking pdf selections to rtf selections.

---
 src/client/util/DragManager.ts                            | 5 +++--
 src/client/views/nodes/DocumentView.tsx                   | 6 ++----
 src/client/views/nodes/WebBox.tsx                         | 4 ++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 ++++-
 src/client/views/pdf/PDFViewer.tsx                        | 6 +++---
 src/fields/documentSchemas.ts                             | 2 +-
 6 files changed, 15 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1bd2bdb9d..9797f5488 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -144,7 +144,7 @@ export namespace DragManager {
         linkSourceDocument: Doc;
         dontClearTextBox?: boolean;
         linkDocument?: Doc;
-        linkDropCallback?: (data: LinkDragData) => void;
+        linkDropCallback?: (data: { linkDocument?: Doc }) => void;
     }
     export class ColumnDragData {
         constructor(colKey: SchemaHeaderField) {
@@ -161,7 +161,7 @@ export namespace DragManager {
             this.annotationDocument = annotationDoc;
             this.offset = [0, 0];
         }
-        linkedToDoc?: boolean;
+        linkDocument?: Doc;
         targetContext: Doc | undefined;
         dragDocument: Doc;
         annotationDocument: Doc;
@@ -169,6 +169,7 @@ export namespace DragManager {
         offset: number[];
         dropAction: dropActionType;
         userDropAction: dropActionType;
+        linkDropCallback?: (data: { linkDocument?: Doc }) => void;
     }
 
     export function MakeDropTarget(
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 41e6d9603..f6360fc87 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -661,10 +661,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (de.complete.annoDragData) {
             /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
             e.stopPropagation();
-            de.complete.annoDragData.linkedToDoc = true;
-
-            const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
-            linkDoc && makeLink(linkDoc);
+            de.complete.annoDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+            de.complete.annoDragData.linkDocument && makeLink(de.complete.annoDragData.linkDocument);
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f9e6227d7..c5d7c3c9f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -499,8 +499,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         if (annotationDoc) {
             DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
                 dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
-                        DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+                    if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) {
+                        e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true;
                     }
                 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 436538eba..311143ff7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -460,8 +460,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         } else if (de.complete.linkDragData) {
             de.complete.linkDragData.linkDropCallback = this.linkDrop;
         }
+        else if (de.complete.annoDragData) {
+            de.complete.annoDragData.linkDropCallback = this.linkDrop;
+        }
     }
-    linkDrop = (data: DragManager.LinkDragData) => {
+    linkDrop = (data: { linkDocument?: Doc }) => {
         const linkDoc = data.linkDocument!;
         const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
         const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1d3b3d994..18be9b679 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -609,11 +609,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         if (annotationDoc) {
             DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
                 dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
-                        const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+                    if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) {
+                        e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing?
-                        if (link) Doc.GetProto(link).followLinkLocation = "default";
                     }
+                    e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument });
                 }
             });
         }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 76b26a9aa..71294c59c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -95,7 +95,7 @@ export const documentSchema = createSchema({
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) 
+    followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, ) 
     hideLinkButton: "boolean",  // whether the blue link counter button should be hidden
     hideAllLinks: "boolean",    // whether all individual blue anchor dots should be hidden
     linkDisplay: "boolean",     // whether a link connection should be shown between link anchor endpoints.
-- 
cgit v1.2.3-70-g09d2


From 9e91c34bc25851ce9a6def9665267c0874b0693c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Sep 2020 14:24:33 -0400
Subject: resurrecting templates with childLayout

---
 .../views/collections/CollectionCarouselView.tsx   | 11 ++++++----
 .../views/collections/CollectionStackingView.tsx   | 18 ++++++++--------
 src/client/views/collections/CollectionSubView.tsx |  4 ++--
 src/client/views/collections/CollectionView.tsx    | 25 ----------------------
 .../views/nodes/ContentFittingDocumentView.tsx     |  5 +++--
 src/client/views/nodes/DocumentView.tsx            |  4 ++--
 src/client/views/nodes/FieldView.tsx               |  1 -
 src/client/views/nodes/ImageBox.tsx                |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 9 files changed, 25 insertions(+), 46 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 8a27f8102..5f63f536d 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -45,17 +45,20 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
     @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
-        return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
+        const layoutTemp = this.props.DataDoc ? true : undefined;
+        const curDoc = this.childLayoutPairs?.[index] || (layoutTemp ? { layout: this.rootDoc, data: this.rootDoc } : undefined);
+        return !(curDoc?.layout instanceof Doc) ? (null) :
             <>
                 <div className="collectionCarouselView-image" key="image">
                     <ContentFittingDocumentView {...this.props}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
                         renderDepth={this.props.renderDepth + 1}
+                        dontRegisterView={layoutTemp} // temporary hack to mark documents as being a template  see imageBox
                         LayoutTemplate={this.props.ChildLayoutTemplate}
                         LayoutTemplateString={this.props.ChildLayoutString}
-                        Document={this.childLayoutPairs[index].layout}
-                        DataDoc={this.childLayoutPairs[index].data}
+                        Document={curDoc.layout}
+                        DataDoc={curDoc.data}
                         PanelHeight={this.panelHeight}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                         bringToFront={returnFalse}
@@ -71,7 +74,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                     <FormattedTextBox key={index} {...this.props}
                         xMargin={NumCast(this.layoutDoc["_carousel-caption-xMargin"])}
                         yMargin={NumCast(this.layoutDoc["_carousel-caption-yMargin"])}
-                        Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"} />
+                        Document={curDoc.layout} DataDoc={undefined} fieldKey={"caption"} />
                 </div>
             </>;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index fb0bce53e..b9a94642a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -77,12 +77,9 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             const dxf = () => this.getDocTransform(d, dref.current!);
             this._docXfs.push({ dxf, width, height });
             const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
-            if (height() < 5) {
-                console.log("here" + height());
-            }
             const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
             return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
-                {this.getDisplayDoc(d, (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc, dxf, width)}
+                {this.getDisplayDoc(d, dxf, width)}
             </div>;
         });
     }
@@ -197,7 +194,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         }, 500);
     }
 
-    getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
+    getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) {
+        const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
         const opacity = () => this.Document._currentFrame === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
         return <ContentFittingDocumentView
@@ -214,7 +212,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             NativeHeight={returnZero}
             NativeWidth={returnZero}
             fitToBox={false}
-            dontRegisterView={BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
+            dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
@@ -247,13 +245,15 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
+        const dataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc;
         const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
-        const nw = NumCast(layoutDoc._nativeWidth);
-        const nh = NumCast(layoutDoc._nativeHeight);
+        const layoutField = Doc.LayoutFieldKey(layoutDoc);
+        const nw = NumCast(layoutDoc._nativeWidth) || NumCast(dataDoc?.[`${layoutField}-nativeWidth`]);
+        const nh = NumCast(layoutDoc._nativeHeight) || NumCast(dataDoc?.[`${layoutField}-nativeHeight`]);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
         if (!layoutDoc._fitWidth && nw && nh) {
             const aspect = nw && nh ? nh / nw : 1;
-            if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid);
+            if (!(this.layoutDoc._columnsFill)) wid = Math.min(this.getDocWidth(d), wid);
             return wid * aspect;
         }
         return layoutDoc._fitWidth ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 94cd7052a..4a8d66050 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -123,8 +123,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             } else {   // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
                 // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
                 // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
-                const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
-                rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
+                // const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
+                // rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
             }
 
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e0c40ce77..bc882f559 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -372,31 +372,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
             this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
     }
-    // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
-    // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
-    // to its children which may be templates.
-    // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
-    @computed get dataField() {
-        return this.dataDoc[this.props.fieldKey];
-    }
-
-    get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
-        const { Document, DataDoc } = this.props;
-        const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
-        return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
-    }
-
-    get childDocList() {
-        return Cast(this.dataField, listSpec(Doc));
-    }
-
-    get childDocs() {
-        const dfield = this.dataField;
-        const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
-        const docs = rawdocs.filter(d => d && !(d instanceof Promise)).map(d => d as Doc);
-        const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript);
-        return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
-    }
 
     onPointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 715faa934..6050d03cc 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -25,8 +25,9 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
             Doc.Layout(this.props.Document);
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
-    nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
+
+    nativeWidth = () => NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
+    nativeHeight = () => NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
     @computed get scaling() {
         const wscale = this.props.PanelWidth() / this.nativeWidth();
         if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f6360fc87..a4ac1c4dc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -119,8 +119,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
     @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); }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index fceeced36..746a906c7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -56,7 +56,6 @@ export interface FieldViewProps {
     NativeWidth: () => number;
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
-
     ChromeHeight?: () => number;
     childLayoutTemplate?: () => Opt<Doc>;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5832a51a7..6fd9b1337 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -250,6 +250,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     _curSuffix = "_m";
 
     resize = (imgPath: string) => {
+        if (this.props.dontRegisterView) return;
         const basePath = imgPath.replace(/_[oms]./, "");
         const curPath = this.dataDoc[this.fieldKey + "-path"];
         const cachedNativeSize = {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 311143ff7..f0dc92b90 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1200,7 +1200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
             e.preventDefault();
         }
-        if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
                 e.stopPropagation();  // if the text box is selected, then it consumes all down events
             }
-- 
cgit v1.2.3-70-g09d2


From 483a4d35ba2c522b889c7bd9b839a0af131ec0bb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Sep 2020 19:26:24 -0400
Subject: updated the way NativeWidth/Height props work so that, when
 specified, they override locally specified values

---
 src/Utils.ts                                       |  2 ++
 src/client/views/DocumentDecorations.tsx           |  6 +++---
 src/client/views/GestureOverlay.tsx                |  2 --
 src/client/views/MainView.tsx                      | 10 ---------
 src/client/views/OverlayView.tsx                   |  2 --
 src/client/views/Palette.tsx                       |  2 --
 src/client/views/PropertiesView.tsx                |  4 ++--
 src/client/views/TemplateMenu.tsx                  |  2 --
 .../views/collections/CollectionCarouselView.tsx   |  4 ++--
 .../views/collections/CollectionLinearView.tsx     |  2 --
 .../views/collections/CollectionSchemaView.tsx     |  2 --
 .../views/collections/CollectionStackingView.tsx   |  8 +++----
 src/client/views/collections/CollectionSubView.tsx |  4 ++--
 .../views/collections/CollectionTreeView.tsx       |  6 ++----
 src/client/views/collections/SchemaTable.tsx       |  2 --
 src/client/views/collections/TabDocView.tsx        |  2 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 14 ++++++------
 .../collectionGrid/CollectionGridView.tsx          |  2 --
 .../CollectionMulticolumnView.tsx                  |  8 ++++---
 .../CollectionMultirowView.tsx                     |  2 --
 src/client/views/nodes/AudioBox.tsx                |  4 ----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 +++---
 .../views/nodes/ContentFittingDocumentView.tsx     | 10 ++++-----
 src/client/views/nodes/DocHolderBox.tsx            |  4 ----
 src/client/views/nodes/DocumentView.tsx            | 10 ++++-----
 src/client/views/nodes/FieldView.tsx               |  4 ++--
 src/client/views/nodes/FilterBox.tsx               |  2 --
 src/client/views/nodes/ImageBox.tsx                | 25 ++++++----------------
 src/client/views/nodes/KeyValuePair.tsx            |  2 --
 src/client/views/nodes/LinkBox.tsx                 |  2 --
 src/client/views/nodes/LinkDocPreview.tsx          |  2 --
 src/client/views/nodes/ScreenshotBox.tsx           |  2 --
 src/client/views/nodes/VideoBox.tsx                |  2 --
 src/client/views/nodes/WebBox.tsx                  |  4 +---
 .../views/nodes/formattedText/DashDocView.tsx      |  2 --
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +---
 .../formattedText/FormattedTextBoxComment.tsx      |  4 ++--
 .../views/nodes/formattedText/RichTextSchema.tsx   |  2 --
 src/client/views/pdf/PDFViewer.tsx                 |  2 --
 .../views/presentationview/PresElementBox.tsx      |  2 --
 src/mobile/AudioUpload.tsx                         |  2 --
 src/mobile/MobileInterface.tsx                     |  2 --
 42 files changed, 52 insertions(+), 133 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 6582e43ef..7dff1ac55 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -414,6 +414,8 @@ export function returnTrue() { return true; }
 
 export function returnFalse() { return false; }
 
+export function returnVal(val1?: number, val2?: number) { return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; }
+
 export function returnOne() { return 1; }
 
 export function returnZero() { return 0; }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 81c1676b0..e5c6f0aa9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,7 @@ import { InkField } from "../../fields/InkField";
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, NumCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
+import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick, returnVal } from "../../Utils";
 import { DocUtils, Docs } from "../documents/Documents";
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from "../util/DragManager";
@@ -453,8 +453,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (e.ctrlKey && !element.props.Document._nativeHeight) element.toggleNativeDimensions();
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 const doc = Document(element.rootDoc);
-                let nwidth = doc._nativeWidth || 0;
-                let nheight = doc._nativeHeight || 0;
+                let nwidth = returnVal(element.NativeWidth?.(), doc._nativeWidth);
+                let nheight = returnVal(element.NativeHeight?.(), doc._nativeHeight);
                 const width = (doc._width || 0);
                 let height = (doc._height || (nheight / nwidth * width));
                 height = !height || isNaN(height) ? 20 : height;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 63711a3cb..6af3a40cf 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -926,8 +926,6 @@ export class GestureOverlay extends Touchable {
                 ContentScaling={returnOne}
                 PanelWidth={this.return300}
                 PanelHeight={this.return300}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 renderDepth={0}
                 backgroundColor={returnEmptyString}
                 focus={emptyFunction}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 318d0368c..d017de457 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -261,8 +261,6 @@ export class MainView extends React.Component {
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
             PanelWidth={this.getPWidth}
             PanelHeight={this.getPHeight}
             focus={emptyFunction}
@@ -318,8 +316,6 @@ export class MainView extends React.Component {
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     rootSelected={returnTrue}
                     removeDocument={returnFalse}
                     onClick={undefined}
@@ -354,8 +350,6 @@ export class MainView extends React.Component {
                 addDocument={undefined}
                 addDocTab={this.addDocTabFunc}
                 pinToPres={emptyFunction}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 rootSelected={returnTrue}
                 removeDocument={returnFalse}
                 onClick={undefined}
@@ -487,8 +481,6 @@ export class MainView extends React.Component {
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
                     ContentScaling={returnOne}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getContentsHeight}
                     renderDepth={0}
@@ -553,8 +545,6 @@ export class MainView extends React.Component {
                 removeDocument={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 PanelWidth={this.getPWidth}
                 PanelHeight={this.getPHeight}
                 renderDepth={0}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index cc0dd0443..4b8049e14 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -193,8 +193,6 @@ export class OverlayView extends React.Component {
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     PanelWidth={returnOne}
                     PanelHeight={returnOne}
                     ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 92c3f09b4..9f08a03e1 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -49,8 +49,6 @@ export default class Palette extends React.Component<PaletteProps> {
                             onClick={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
                             PanelWidth={() => window.screen.width}
                             PanelHeight={() => window.screen.height}
                             renderDepth={0}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 041eec2b4..7da56721a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -287,9 +287,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     fitToBox={true}
                     FreezeDimensions={true}
                     NativeWidth={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero}
+                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : undefined}
                     NativeHeight={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : returnZero}
+                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : undefined}
                     PanelWidth={panelWidth}
                     PanelHeight={panelHeight}
                     focus={returnFalse}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 870af03aa..c1878115d 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -150,8 +150,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 select={emptyFunction}
                 renderDepth={1}
                 addDocTab={returnFalse}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 PanelWidth={this.return100}
                 PanelHeight={this.return100}
                 treeViewHideHeaderFields={true}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 5f63f536d..034d006f2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -13,7 +13,7 @@ import { Doc } from '../../../fields/Doc';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../fields/ObjectField';
-import { returnFalse } from '../../../Utils';
+import { returnFalse, returnZero } from '../../../Utils';
 import { ScriptField } from '../../../fields/ScriptField';
 
 type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
@@ -54,9 +54,9 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
                         renderDepth={this.props.renderDepth + 1}
-                        dontRegisterView={layoutTemp} // temporary hack to mark documents as being a template  see imageBox
                         LayoutTemplate={this.props.ChildLayoutTemplate}
                         LayoutTemplateString={this.props.ChildLayoutString}
+                        NativeWidth={layoutTemp ? returnZero : this.props.NativeWidth}
                         Document={curDoc.layout}
                         DataDoc={curDoc.data}
                         PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index ea050011a..22c1f51a6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -153,8 +153,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
                                 ContentScaling={returnOne}
-                                NativeHeight={returnZero}
-                                NativeWidth={returnZero}
                                 PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
                                 PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
                                 renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 1b68c0e1a..332a2001f 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -429,8 +429,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 <ContentFittingDocumentView
                     Document={this.previewDocument}
                     DataDoc={undefined}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     fitToBox={true}
                     FreezeDimensions={true}
                     focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b9a94642a..84173d3ba 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -12,7 +12,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, returnOne, returnZero, setupMoveUpEvents, Utils, smoothScroll } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, smoothScroll, returnVal } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -209,8 +209,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
             fitToBox={false}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
@@ -481,8 +479,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
 
 
-    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth) || this.props.NativeWidth() || 0; }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight) || this.props.NativeHeight() || 0; }
+    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc._nativeWidth)); }
+    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc._nativeHeight)); }
 
     @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4a8d66050..8e4c89922 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -31,8 +31,8 @@ export interface CollectionViewProps extends FieldViewProps {
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
     rootSelected: (outsideReaction?: boolean) => boolean;
     fieldKey: string;
-    NativeWidth: () => number;
-    NativeHeight: () => number;
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f13fee776..7b3e95404 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -357,8 +357,8 @@ class TreeView extends React.Component<TreeViewProps> {
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     FreezeDimensions={true}
-                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : returnZero}
-                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : returnZero}
+                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
+                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
                     PanelWidth={panelWidth}
                     PanelHeight={panelHeight}
                     focus={returnFalse}
@@ -464,8 +464,6 @@ class TreeView extends React.Component<TreeViewProps> {
                 ContentScaling={returnOne}
                 PanelWidth={this.truncateTitleWidth}
                 PanelHeight={returnZero}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 contextMenuItems={this.contextMenuItems}
                 opacity={returnOne}
                 renderDepth={1}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 1fb7aa04a..854254195 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -570,8 +570,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                     ref="overlay"><ContentFittingDocumentView
                         Document={this._showDoc}
                         DataDoc={this._showDataDoc}
-                        NativeHeight={returnZero}
-                        NativeWidth={returnZero}
                         fitToBox={true}
                         FreezeDimensions={true}
                         focus={emptyFunction}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index fb4f5c366..93d830b8a 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -306,8 +306,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 ContentScaling={returnOne}
                 PanelWidth={this.returnMiniSize}
                 PanelHeight={this.returnMiniSize}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 ScreenToLocalTransform={this.ScreenToLocalTransform}
                 renderDepth={0}
                 whenActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 86460eee1..aa334df6c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -12,7 +12,7 @@ import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
@@ -107,8 +107,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
-    @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
-    @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
+    @computed get nativeWidth() { return this.fitToContent ? 0 : returnVal(this.props.NativeWidth?.(), NumCast(this.Document._nativeWidth)); }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : returnVal(this.props.NativeHeight?.(), NumCast(this.Document._nativeHeight)); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
@@ -942,8 +942,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             moveDocument: this.props.moveDocument,
             pinToPres: this.props.pinToPres,
             whenActiveChanged: this.props.whenActiveChanged,
-            NativeHeight: returnZero,
-            NativeWidth: returnZero,
             fitToBox: false,
             DataDoc: childData,
             Document: childLayout,
@@ -1218,7 +1216,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
     }
 
     @undoBatch
@@ -1411,8 +1409,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get contentScaling() {
         if (this.props.annotationsKey && !this.props.forceScaling) return 0;
-        const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
-        const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
+        const nw = returnVal(this.props.NativeWidth?.(), NumCast(this.Document._nativeWidth));
+        const nh = returnVal(this.props.NativeHeight?.(), NumCast(this.Document._nativeHeight));
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index df4d71526..bc04c79f9 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -165,8 +165,6 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
             {...this.props}
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
             backgroundColor={this.props.backgroundColor}
             ContainingCollectionDoc={this.props.Document}
             PanelWidth={width}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 0afcab5a3..b3f5d1c0f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -213,19 +213,19 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         return this.props.addDocTab(doc, where);
     }
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+        const layoutTemp = this.props.DataDoc ? true : undefined;
         return <ContentFittingDocumentView
             Document={layout}
-            DataDoc={layout.resolvedDataDoc as Doc}
+            DataDoc={layout.resolvedDataDoc as Doc || (layoutTemp ? layout : undefined)}
             backgroundColor={this.props.backgroundColor}
             LayoutTemplate={this.props.ChildLayoutTemplate}
             LayoutTemplateString={this.props.ChildLayoutString}
+            NativeWidth={layoutTemp ? returnZero : this.props.NativeWidth}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
             fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
@@ -248,6 +248,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             ContentScaling={returnOne}
         />;
     }
+    @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
     /**
      * @returns the resolved list of rendered child documents, displayed
      * at their resolved pixel widths, each separated by a resizer. 
@@ -257,6 +258,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         const { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
+        const layoutTemp = this.props.DataDoc ? true : undefined;
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 53825eece..9a4fb0627 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -224,8 +224,6 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
             fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 0e3c4462c..5e7f8dfda 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -520,8 +520,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                 Document={mark}
                 focus={() => this.playLink(mark)}
                 pointerEvents={true}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 rootSelected={returnFalse}
                 LayoutTemplate={undefined}
                 ContainingCollectionDoc={this.props.Document}
@@ -607,8 +605,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                                     <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container`} key={l[Id]} style={{ left: `${startTime / this.audioDuration * 100}%` }} onClick={e => e.stopPropagation()}>
                                         <DocumentView {...this.props}
                                             Document={l}
-                                            NativeHeight={returnZero}
-                                            NativeWidth={returnZero}
                                             rootSelected={returnFalse}
                                             ContainingCollectionDoc={this.props.Document}
                                             parentActive={returnTrue}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 16b2f7c2a..bfa66272e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -11,7 +11,7 @@ import { Document } from "../../../fields/documentSchemas";
 import { TraceMobx } from "../../../fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import { List } from "../../../fields/List";
-import { numberRange, smoothScroll } from "../../../Utils";
+import { numberRange, smoothScroll, returnVal } from "../../../Utils";
 import { ComputedField } from "../../../fields/ScriptField";
 import { listSpec } from "../../../fields/Schema";
 import { DocumentType } from "../../documents/DocumentTypes";
@@ -55,8 +55,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
     @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc._nativeWidth, this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc._nativeHeight, this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
 
     public static getValues(doc: Doc, time: number) {
         const timecode = Math.round(time);
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 6050d03cc..30458b9f8 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -6,7 +6,7 @@ import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction } from "../../../Utils";
+import { emptyFunction, returnVal } from "../../../Utils";
 import { dropActionType } from "../../util/DragManager";
 import { CollectionView } from "../collections/CollectionView";
 import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
@@ -26,8 +26,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
 
-    nativeWidth = () => NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
-    nativeHeight = () => NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
+    nativeWidth = () => returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())));
+    nativeHeight = () => returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc?.[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())));
     @computed get scaling() {
         const wscale = this.props.PanelWidth() / this.nativeWidth();
         if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
@@ -40,8 +40,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;
 
-    @computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
-    @computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
+    @computed get panelWidth() { return this.nativeWidth() && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
+    @computed get panelHeight() { return this.nativeHeight() && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
     private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index b3b7cc4f3..91f96135f 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -135,8 +135,6 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                     pinToPres={this.props.pinToPres}
                     ScreenToLocalTransform={this.getTransform}
                     renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     PanelWidth={this.pwidth}
                     PanelHeight={this.pheight}
                     focus={this.props.focus}
@@ -166,8 +164,6 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                     pinToPres={this.props.pinToPres}
                     ScreenToLocalTransform={this.getTransform}
                     renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     PanelWidth={this.pwidth}
                     PanelHeight={this.pheight}
                     focus={this.props.focus}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a4ac1c4dc..9992874bf 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, returnVal } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -49,8 +49,8 @@ export interface DocumentViewProps {
     docFilters: () => string[];
     searchFilterDocs: () => Doc[];
     FreezeDimensions?: boolean;
-    NativeWidth: () => number;
-    NativeHeight: () => number;
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
     Document: Doc;
     DataDoc?: Doc;
     getView?: (view: DocumentView) => any;
@@ -119,8 +119,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0))); }
+    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0))); }
     @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); }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 746a906c7..4ec5bc534 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -52,8 +52,8 @@ export interface FieldViewProps {
     PanelHeight: () => number;
     PanelPosition?: string;
     overflow?: boolean;
-    NativeHeight: () => number;
-    NativeWidth: () => number;
+    NativeHeight?: () => number;
+    NativeWidth?: () => number;
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index a97f8c541..eab365445 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -164,8 +164,6 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     PanelWidth={this.props.PanelWidth}
                     PanelHeight={this.props.PanelHeight}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     LibraryPath={emptyPath}
                     rootSelected={this.props.rootSelected}
                     renderDepth={1}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 952e92ca6..beb6ba6a8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -75,10 +75,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     componentDidMount() {
         this._pathDisposer = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
             ({ nativeSize, width }) => {
-                this.layoutDoc._nativeWidth = nativeSize.nativeWidth;
-                this.layoutDoc._nativeHeight = nativeSize.nativeHeight;
-                this.layoutDoc._nativeOrientation = nativeSize.nativeOrientation;
-                this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
+                if (this.props.NativeWidth?.() !== 0 || !this.layoutDoc._height) {
+                    this.layoutDoc._nativeWidth = nativeSize.nativeWidth;
+                    this.layoutDoc._nativeHeight = nativeSize.nativeHeight;
+                    this.layoutDoc._nativeOrientation = nativeSize.nativeOrientation;
+                    this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
+                }
             },
             { fireImmediately: true });
     }
@@ -164,19 +166,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
             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" });
-                // funcs.push({
-                //     description: "Reset Native Dimensions", event: action(async () => {
-                //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
-                //         const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
-                //         if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
-                //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
-                //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
-                //         } else {
-                //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
-                //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
-                //         }
-                //     }), icon: "expand-arrows-alt"
-                // });
 
                 const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
                 const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
@@ -434,8 +423,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 forceScaling={true}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 annotationsKey={this.annotationKey}
                 isAnnotationOverlay={true}
                 focus={this.props.focus}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 74d10d087..e531083bf 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -71,8 +71,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-            NativeHeight: returnZero,
-            NativeWidth: returnZero,
             PanelWidth: this.props.PanelWidth,
             PanelHeight: this.props.PanelHeight,
             addDocTab: returnFalse,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index a067f23af..64ae1051b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -22,8 +22,6 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
             <CollectionTreeView {...this.props}
                 ChromeHeight={returnZero}
                 overrideDocuments={[this.dataDoc]}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
                 annotationsKey={""}
                 dontRegisterView={true}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index a70736d90..875026944 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -114,8 +114,6 @@ export class LinkDocPreview extends React.Component<Props> {
                 whenActiveChanged={returnFalse}
                 bringToFront={returnFalse}
                 ContentScaling={returnOne}
-                NativeWidth={returnZero}
-                NativeHeight={returnZero}
                 backgroundColor={this.props.backgroundColor} />;
     }
 
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 5d51c420b..34ad7f3be 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -172,8 +172,6 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     annotationsKey={""}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d50a10bdd..79123c081 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -368,8 +368,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     annotationsKey={this.annotationKey}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c5d7c3c9f..41c310a96 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -369,7 +369,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
     }
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
@@ -650,8 +650,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={this.props.PanelWidth}
                             annotationsKey={this.annotationKey}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
                             VisibleHeight={this.visibleHeiht}
                             focus={this.props.focus}
                             setPreviewCursor={this.setPreviewCursor}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 3b77735a7..90655bad6 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -245,8 +245,6 @@ export class DashDocView extends React.Component<IDashDocView> {
                             addDocTab={this._textBox.props.addDocTab}
                             pinToPres={returnFalse}
                             renderDepth={self._textBox.props.renderDepth + 1}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
                             PanelWidth={finalLayout[WidthSym]}
                             PanelHeight={finalLayout[HeightSym]}
                             focus={this.outerFocus}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f0dc92b90..335605a00 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -562,7 +562,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.()||0, this.props.NativeHeight?.()||0);
     }
 
     public static get DefaultLayout(): Doc | string | undefined {
@@ -1565,8 +1565,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             <CollectionFreeFormView {...this.props}
                                 PanelHeight={this.props.PanelHeight}
                                 PanelWidth={this.sidebarWidth}
-                                NativeHeight={returnZero}
-                                NativeWidth={returnZero}
                                 scaleField={this.annotationKey + "-scale"}
                                 annotationsKey={this.annotationKey}
                                 isAnnotationOverlay={false}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index b4f648273..8524786c8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -319,8 +319,8 @@ export class FormattedTextBoxComment {
                                             whenActiveChanged={returnFalse}
                                             bringToFront={returnFalse}
                                             ContentScaling={returnOne}
-                                            NativeWidth={() => target._nativeWidth ? NumCast(target._nativeWidth) : 0}
-                                            NativeHeight={() => target._nativeHeight ? NumCast(target._nativeHeight) : 0}
+                                            NativeWidth={target._nativeWidth ? (() => NumCast(target._nativeWidth)) : undefined}
+                                            NativeHeight={target._natvieHeight ? (() => NumCast(target._nativeHeight)) : undefined}
                                         />
                                     </div>
                                 </div>;
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index f0bacb735..a80d2639d 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -145,8 +145,6 @@ export class DashDocView {
                     addDocTab={this._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={self._textBox.props.renderDepth + 1}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     PanelWidth={finalLayout[WidthSym]}
                     PanelHeight={finalLayout[HeightSym]}
                     focus={this.outerFocus}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 18be9b679..65eb23a06 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -679,8 +679,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
                 PanelWidth={this.panelHeight}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
                 dropAction={"alias"}
                 VisibleHeight={this.visibleHeight}
                 focus={this.props.focus}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index c33637d0f..048d3a3d0 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -107,8 +107,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     ContentScaling={returnOne}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                 />
                 <div className="presElementBox-embeddedMask" />
             </div>;
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 5e86246c5..81b3869ae 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -94,8 +94,6 @@ export class AudioUpload extends React.Component {
                         ContentScaling={returnOne}
                         PanelWidth={() => 600}
                         PanelHeight={() => 400}
-                        NativeHeight={returnZero}
-                        NativeWidth={returnZero}
                         renderDepth={0}
                         focus={emptyFunction}
                         backgroundColor={() => "rgba(0,0,0,0)"}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index ae2c07c8e..cb5191e61 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -213,8 +213,6 @@ export class MobileInterface extends React.Component {
                     ContentScaling={returnOne}
                     PanelWidth={this.returnWidth}
                     PanelHeight={this.returnHeight}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
                     renderDepth={0}
                     focus={emptyFunction}
                     backgroundColor={this.whitebackground}
-- 
cgit v1.2.3-70-g09d2


From f5c00261d8d936abdec22abfae94dd972c0f9e4a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Sep 2020 22:57:51 -0400
Subject: fixed presbox, videobox and some others to set NativeWidth/Height
 properly.

---
 src/client/views/MainView.tsx                       |  4 ----
 src/client/views/PropertiesView.tsx                 | 21 +++++----------------
 .../views/collections/CollectionStackingView.tsx    | 16 ++--------------
 src/client/views/collections/CollectionSubView.tsx  |  1 -
 src/client/views/collections/CollectionView.tsx     |  1 +
 src/client/views/collections/TabDocView.tsx         |  8 ++++----
 .../CollectionMulticolumnView.tsx                   |  1 -
 .../views/nodes/ContentFittingDocumentView.scss     |  1 -
 .../views/nodes/ContentFittingDocumentView.tsx      |  2 +-
 src/client/views/nodes/ScreenshotBox.tsx            |  4 ++--
 src/client/views/nodes/VideoBox.tsx                 |  4 ++--
 src/client/views/nodes/WebBox.tsx                   |  4 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  6 +++---
 src/client/views/pdf/PDFViewer.tsx                  |  4 ++--
 14 files changed, 24 insertions(+), 53 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d017de457..bdf8c83e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -581,8 +581,6 @@ export class MainView extends React.Component {
                     focus={returnFalse}
                     PanelWidth={() => 500}
                     PanelHeight={() => 800}
-                    NativeHeight={() => 500}
-                    NativeWidth={() => 800}
                     ContentScaling={returnOne}
                     docFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
@@ -648,8 +646,6 @@ export class MainView extends React.Component {
                             focus={returnFalse}
                             PanelWidth={() => 500}
                             PanelHeight={() => 800}
-                            NativeHeight={() => 500}
-                            NativeWidth={() => 800}
                             ContentScaling={returnOne}
                             docFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 7da56721a..d70f4b332 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -80,21 +80,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
 
-    @action
     rtfWidth = () => {
-        if (this.selectedDoc) {
-            return Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
-        } else {
-            return 0;
-        }
+        return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
     }
-    @action
     rtfHeight = () => {
-        if (this.selectedDoc) {
-            return this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
-        } else {
-            return 0;
-        }
+        return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
     }
 
     @action
@@ -286,10 +276,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     backgroundColor={this.previewBackground}
                     fitToBox={true}
                     FreezeDimensions={true}
-                    NativeWidth={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : undefined}
-                    NativeHeight={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : undefined}
+                    dontCenter={true}
+                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
+                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
                     PanelWidth={panelWidth}
                     PanelHeight={panelHeight}
                     focus={returnFalse}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a7276a962..cbc62be91 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -128,19 +128,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         return fields;
     }
 
-    getSimpleDocHeight(d?: Doc) {
-        if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
-        const nw = NumCast(layoutDoc._nativeWidth);
-        const nh = NumCast(layoutDoc._nativeHeight);
-        let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
-        if (!layoutDoc._fitWidth && nw && nh) {
-            const aspect = nw && nh ? nh / nw : 1;
-            if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid);
-            return wid * aspect;
-        }
-        return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
-    }
     componentDidMount() {
         super.componentDidMount?.();
 
@@ -209,8 +196,9 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            NativeWidth={this.props.childIgnoreNativeSize ? returnZero : undefined}
+            NativeWidth={this.props.childIgnoreNativeSize ? returnZero : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
             NativeHeight={this.props.childIgnoreNativeSize ? returnZero : undefined}
+            dontCenter={this.props.childIgnoreNativeSize ? true : false}
             fitToBox={false}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 37212a0fa..81f3ddad2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -28,7 +28,6 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     VisibleHeight?: () => number;
-    childIgnoreNativeSize?: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
     rootSelected: (outsideReaction?: boolean) => boolean;
     fieldKey: string;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ef3128bc0..6939399e6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -73,6 +73,7 @@ export interface CollectionViewCustomProps {
     filterAddDocument?: (doc: Doc | Doc[]) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     childOpacity?: () => number;
     hideFilter?: true;
+    childIgnoreNativeSize?: boolean;
 }
 
 export interface CollectionRenderProps {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 93d830b8a..f3d2aaa8f 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -228,8 +228,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         }
         return Transform.Identity();
     }
-    get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
-    get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
+    @computed get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    @computed get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
 
     // adds a tab to the layout based on the locaiton parameter which can be:
     //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
@@ -345,8 +345,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 ContentScaling={this.contentScaling}
                 PanelWidth={this.panelWidth}
                 PanelHeight={this.panelHeight}
-                NativeHeight={this.nativeHeight}
-                NativeWidth={this.nativeWidth}
+                NativeHeight={this.nativeHeight() ? this.nativeHeight : undefined}
+                NativeWidth={this.nativeWidth() ? this.nativeWidth : undefined}
                 ScreenToLocalTransform={this.ScreenToLocalTransform}
                 renderDepth={0}
                 parentActive={returnTrue}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 556cf79cd..0e4785d36 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -256,7 +256,6 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         const { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
-        const layoutTemp = this.props.DataDoc ? true : undefined;
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
diff --git a/src/client/views/nodes/ContentFittingDocumentView.scss b/src/client/views/nodes/ContentFittingDocumentView.scss
index eb2d93b9a..50562951a 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.scss
+++ b/src/client/views/nodes/ContentFittingDocumentView.scss
@@ -3,7 +3,6 @@
 .contentFittingDocumentView {
     position: relative;
     display: flex;
-    align-items: center;
 
     .contentFittingDocumentView-previewDoc {
         position: relative;
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 1fe7e6207..55149a44b 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -62,7 +62,7 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
             {!this.props.Document || !this.props.PanelWidth ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc"
                     style={{
-                        transform: !this.props.dontCenter ? `translate(${this.centeringOffset}px, 0px)` : undefined,
+                        transform: !this.props.dontCenter ? `translate(${this.centeringOffset}px, ${this.centeringYOffset}px)` : undefined,
                         borderRadius: this.borderRounding,
                         height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight() / this.nativeWidth() * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
                         width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth()
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 34ad7f3be..f13f373d0 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -9,7 +9,7 @@ import { InkTool } from "../../../fields/InkField";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast } from "../../../fields/Types";
 import { VideoField } from "../../../fields/URLField";
-import { emptyFunction, returnFalse, returnOne, returnZero, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, returnZero, Utils, OmitKeys, OmitKeys } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -169,7 +169,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
         return (<div className="videoBox" onContextMenu={this.specificContextMenu}
             style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
             <div className="videoBox-viewer" >
-                <CollectionFreeFormView {...this.props}
+                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
                     annotationsKey={""}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 79123c081..925bc9dd6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -8,7 +8,7 @@ import { InkTool } from "../../../fields/InkField";
 import { createSchema, makeInterface } from "../../../fields/Schema";
 import { Cast, StrCast } from "../../../fields/Types";
 import { VideoField } from "../../../fields/URLField";
-import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
+import { Utils, emptyFunction, returnOne, returnZero, OmitKeys, OmitKeys } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -365,7 +365,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         return (<div className="videoBox" onContextMenu={this.specificContextMenu}
             style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
             <div className="videoBox-viewer" >
-                <CollectionFreeFormView {...this.props}
+                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
                     annotationsKey={this.annotationKey}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 41c310a96..f841fa6e1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
 import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
-import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue } from "../../../Utils";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue, OmitKeys } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -646,7 +646,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                         height: NumCast(this.layoutDoc.scrollHeight),
                         pointerEvents: this.layoutDoc._isBackground ? "none" : undefined
                     }}>
-                        <CollectionFreeFormView {...this.props}
+                        <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={this.props.PanelWidth}
                             annotationsKey={this.annotationKey}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 335605a00..f7777f83c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -23,7 +23,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
 import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -562,7 +562,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.()||0, this.props.NativeHeight?.()||0);
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
     }
 
     public static get DefaultLayout(): Doc | string | undefined {
@@ -1562,7 +1562,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
                         <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
                             style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                            <CollectionFreeFormView {...this.props}
+                            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                                 PanelHeight={this.props.PanelHeight}
                                 PanelWidth={this.sidebarWidth}
                                 scaleField={this.annotationKey + "-scale"}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 65eb23a06..e0195a100 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -13,7 +13,7 @@ import { ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
 import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils, OmitKeys } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Networking } from "../../Network";
@@ -673,7 +673,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @computed get overlayLayer() {
         return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay"
             style={{ transform: `scale(${this._zoomed})` }}>
-            <CollectionFreeFormView {...this.props}
+            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                 LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
                 annotationsKey={this.annotationKey}
                 setPreviewCursor={this.setPreviewCursor}
-- 
cgit v1.2.3-70-g09d2


From b8300497de3d496015c719ad59df9b1d6d4b0e56 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Sep 2020 23:57:23 -0400
Subject: fixed event handling on embedded textboxes - dash field views need to
 stopPropagation() on pointerdown events, and textBoxes need to handle scroll
 events when they are rootSelected() (ie, in a template)

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

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

diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 65695d22a..cb7ffb0e7 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -182,7 +182,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         if (e.button === 0 && !e.ctrlKey) {
-            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, () => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
+            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
             this._createAliasSelected = false;
         }
     }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f2658e77e..f14a57e31 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -106,7 +106,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                     ref={r => {
                         r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
                         r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
-                        r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
+                        r?.addEventListener("pointerdown", action((e) => {
+                            this._showEnumerables = true;
+                            e.stopPropagation();
+                        }));
                     }} >
                     {strVal}
                 </span>;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 311143ff7..f1a1726b2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1286,7 +1286,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
-        if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+        if ((this.props.rootSelected(true) || this.props.isSelected(true)) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
             e.stopPropagation();
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 702242a07451fc89d176ffcd7cc881928b2bc23c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Sep 2020 17:28:08 -0400
Subject: changes to switch from 'pt' units to 'px' for fonts.  fixxes to
 presentations for treeview outlines.

---
 solr-8.3.1/bin/solr-8983.pid                       |   2 +-
 src/client/util/CurrentUserUtils.ts                |  17 +-
 src/client/util/DragManager.ts                     |   2 +-
 src/client/util/SettingsManager.tsx                |   4 +-
 src/client/util/UndoManager.ts                     |   2 +
 src/client/views/MainView.tsx                      |   3 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/animationtimeline/Timeline.tsx    |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |  10 +-
 .../views/collections/CollectionTreeView.scss      |   3 +-
 .../views/collections/CollectionTreeView.tsx       | 295 ++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  13 +-
 src/client/views/nodes/PresBox.tsx                 |  28 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  15 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |  76 +++---
 .../views/nodes/formattedText/RichTextMenu.tsx     |  36 +--
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/fields/Doc.ts                                  |   9 +-
 18 files changed, 314 insertions(+), 209 deletions(-)

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

diff --git a/solr-8.3.1/bin/solr-8983.pid b/solr-8.3.1/bin/solr-8983.pid
index 1f77d281d..cfdfb83dc 100644
--- a/solr-8.3.1/bin/solr-8983.pid
+++ b/solr-8.3.1/bin/solr-8983.pid
@@ -1 +1 @@
-1731
+1750
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 20799fa26..c2c9892be 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -208,7 +208,7 @@ export class CurrentUserUtils {
             details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
 
             const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
-            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" };
+            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" };
             const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true };
 
             const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
@@ -240,7 +240,7 @@ export class CurrentUserUtils {
             });
 
             const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
-            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" };
+            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" };
             const detailView = Docs.Create.StackingDocument([carousel], { ...shared, ...detailViewOpts, system: true });
             detailView.isTemplateDoc = makeTemplate(detailView);
 
@@ -419,7 +419,12 @@ export class CurrentUserUtils {
             ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
         }
         if (doc.emptySlide === undefined) {
-            doc.emptySlide = Docs.Create.TreeDocument([], { title: "slide", treeViewOutlineMode: true, _backgroundColor: "transparent", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
+            const textDoc = Docs.Create.TextDocument("Slide", { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewOutlineMode: true, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, _backgroundColor: "transparent", system: true, cloneFieldFilter: new List<string>(["system"]) });
+            Doc.GetProto(textDoc).layout = CollectionView.LayoutString("data");
+            Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
+            Doc.GetProto(textDoc).data = new List<Doc>([]);
+            FormattedTextBox.SelectOnLoad = textDoc[Id];
+            doc.emptySlide = textDoc;
         }
         if (doc.emptyComparison === undefined) {
             doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
@@ -641,13 +646,13 @@ export class CurrentUserUtils {
     // Sets up the title of the button
     static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
         ...opts,
-        dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", system: true
+        dropAction: undefined, title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", system: true
     }) as any as Doc
 
     // Sets up the description of the button
     static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
         ...opts,
-        dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true
+        dropAction: undefined, title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true
     }) as any as Doc
 
 
@@ -953,7 +958,7 @@ export class CurrentUserUtils {
         doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
         doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
         doc.activeDash = StrCast(doc.activeDash, "0");
-        doc.fontSize = StrCast(doc.fontSize, "12pt");
+        doc.fontSize = StrCast(doc.fontSize, "12px");
         doc.fontFamily = StrCast(doc.fontFamily, "Arial");
         doc.fontColor = StrCast(doc.fontColor, "black");
         doc.fontHighlight = StrCast(doc.fontHighlight, "");
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9797f5488..367155c90 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -329,7 +329,7 @@ export namespace DragManager {
             dragLabel = document.createElement("div");
             dragLabel.className = "dragManager-dragLabel";
             dragLabel.style.zIndex = "100001";
-            dragLabel.style.fontSize = "10pt";
+            dragLabel.style.fontSize = "10px";
             dragLabel.style.position = "absolute";
             // dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
             dragDiv.appendChild(dragLabel);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 0a1890ca4..c9605c620 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -81,7 +81,7 @@ export class SettingsManager extends React.Component<{}> {
         </div>;
 
         const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
-        const fontSizes = ["7pt", "8pt", "9pt", "10pt", "12pt", "14pt", "16pt", "18pt", "20pt", "24pt", "32pt", "48pt", "72pt"];
+        const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
 
         return <div className="preferences-content">
             <div className="preferences-color">
@@ -93,7 +93,7 @@ export class SettingsManager extends React.Component<{}> {
                 <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
                     {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
                 </select>
-                <select className="size-select" style={{ marginRight: "10px" }} onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}>
+                <select className="size-select" style={{ marginRight: "10px" }} onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
                     {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
                 </select>
                 <div>
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index c7b7bb215..0f7ad6d0a 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -128,6 +128,7 @@ export namespace UndoManager {
     }
 
     export function StartBatch(batchName: string): Batch {
+        // console.log("Start " + batchCounter + " " + batchName);
         batchCounter++;
         if (batchCounter > 0 && currentBatch === undefined) {
             currentBatch = [];
@@ -137,6 +138,7 @@ export namespace UndoManager {
 
     const EndBatch = action((cancel: boolean = false) => {
         batchCounter--;
+        // console.log("End " + batchCounter);
         if (batchCounter === 0 && currentBatch?.length) {
             if (!cancel) {
                 undoStack.push(currentBatch);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 379577a25..371ee3960 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
+import * as far from '@fortawesome/free-regular-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -141,7 +142,7 @@ export class MainView extends React.Component {
             fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
             fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
             fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
-            fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle,
+            fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle,
             fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
             fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
             fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 5a9402d70..53261d8de 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -45,8 +45,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
     @computed get selectedDocumentView() {
+        if (PresBox.Instance?._selectedArray.length) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
         if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0];
-        if (PresBox.Instance && PresBox.Instance._selectedArray.length) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
         return undefined;
     }
     @computed get isPres(): boolean {
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 1131ec874..9269e6f33 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -382,10 +382,10 @@ export class Timeline extends React.Component<FieldViewProps> {
             const ttime = `Total: ${this.toReadTime(this._time)}`;
             return (
                 <div style={{ flexDirection: "column" }}>
-                    <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+                    <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
                         {ctime}
                     </div>
-                    <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+                    <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
                         {ttime}
                     </div>
                 </div>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cb989d348..f3ed89c78 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -251,7 +251,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
     }
 
     @computed get subChrome() {
-        switch (this.props.type) {
+        switch (this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type) { // bcz: ARgh!  hack to get menu for tree view outline items
             default: return this.otherSubChrome;
             case CollectionViewType.Invalid:
             case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
@@ -528,16 +528,14 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     }
 
     @computed get selectedDocumentView() {
-        if (SelectionManager.SelectedDocuments().length) {
-            return SelectionManager.SelectedDocuments()[0];
-        } else { return undefined; }
+        return SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
     }
     @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
     @computed get isText() {
         if (this.selectedDoc) {
             const layoutField = Doc.LayoutField(this.selectedDoc);
-            return StrCast(layoutField).includes("FormattedText") ||
-                (layoutField instanceof Doc && StrCast(layoutField.layout).includes("FormattedText"));
+            const layoutStr = this.selectedDocumentView?.props.LayoutTemplateString || StrCast(layoutField);
+            return layoutStr.includes("FormattedText") || StrCast((layoutField as Doc)?.layout).includes("FormattedText");
         }
         else return false;
     }
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 3f437b799..09a46c30f 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -34,8 +34,8 @@
         position: relative;
         width: 15px;
         color: $intermediate-color;
-        margin-top: 3px;
         transform: scale(0.5);
+        display: inline-block;
     }
 
     .bullet {
@@ -127,6 +127,7 @@
 .treeViewItem-header {
     border: transparent 1px solid;
     display: flex;
+    align-items: center;
 
     .editableView-container-editing-oneLine {
         min-width: 15px;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d1a2fd33e..7765834f1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -4,7 +4,6 @@ import { observer } from "mobx-react";
 import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym, DocListCastOrNull } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
-import { PrefetchProxy } from '../../../fields/Proxy';
 import { Document, listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -22,16 +21,17 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from "../EditableView";
 import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
-import { ImageBox } from '../nodes/ImageBox';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { Templates } from '../Templates';
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
-import { CollectionViewType } from './CollectionView';
+import { CollectionViewType, CollectionView } from './CollectionView';
 import React = require("react");
-import { makeTemplate } from '../../util/DropConverter';
 import { TraceMobx } from '../../../fields/util';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { RichTextField } from '../../../fields/RichTextField';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 
 export interface TreeViewProps {
     document: Doc;
@@ -90,13 +90,14 @@ class TreeView extends React.Component<TreeViewProps> {
     get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
     get displayName() { return "TreeView(" + this.doc.title + ")"; }  // this makes mobx trace() statements more descriptive
     get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; }
-    get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode ? "layout" : "fields"); }
+    get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); }
     get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); }
     @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
     set treeViewOpen(c: boolean) {
         if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c;
         else this.doc.treeViewOpen = this._overrideTreeViewOpen = c;
     }
+    @computed get outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; }
     @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
@@ -122,12 +123,10 @@ class TreeView extends React.Component<TreeViewProps> {
         return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc);
     }
     @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
+        this.props.treeView.props.select(false);
         return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
     }
-    @undoBatch @action removeDoc = (doc: Doc | Doc[]) => {
-        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
-            flg && Doc.RemoveDocFromList(this.doc, Doc.LayoutFieldKey(this.doc), doc), true);
-    }
+    @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc));
 
     constructor(props: any) {
         super(props);
@@ -144,10 +143,15 @@ class TreeView extends React.Component<TreeViewProps> {
         ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc);
     }
 
+    componentWillUnmount() {
+        document.removeEventListener("pointermove", this.onDragMove, true);
+    }
+
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
         if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
             this._header!.current!.className = "treeViewItem-header";
+            document.removeEventListener("pointermove", this.onDragMove, true);
             document.addEventListener("pointermove", this.onDragMove, true);
         }
     }
@@ -171,6 +175,24 @@ class TreeView extends React.Component<TreeViewProps> {
         e.stopPropagation();
     }
 
+    public static makeTextBullet() {
+        const bullet = Docs.Create.TextDocument("", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 2000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
+        Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
+        Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
+        Doc.GetProto(bullet).data = new List<Doc>([]);
+        Doc.SetInPlace(bullet, "editTitle", "*", false);
+        FormattedTextBox.SelectOnLoad = bullet[Id];
+        return bullet;
+    }
+
+    makeTextCollection = () => {
+        Doc.SetInPlace(this.doc, "editTitle", undefined, false);
+        const bullet = TreeView.makeTextBullet();
+        const added = this.props.addDocument(bullet);
+        bullet.context = this.props.treeView.Document;
+        return added;
+    }
+
     editableView = (key: string, style?: string) => (<EditableView
         oneLine={true}
         display={"inline-block"}
@@ -182,27 +204,14 @@ class TreeView extends React.Component<TreeViewProps> {
         fontSize={12}
         GetValue={() => StrCast(this.doc[key])}
         SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
-            if (this.props.treeView.doc.treeViewOutlineMode && enterKey) {
+            if (this.outlineMode && enterKey) {
                 Doc.SetInPlace(this.doc, key, value, false);
-                const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
-                Doc.SetInPlace(this.doc, "editTitle", undefined, false);
-                Doc.SetInPlace(doc, "editTitle", "*", false);
-                this.props.addDocument(doc);
-                doc.context = this.props.treeView.Document;
+                this.makeTextCollection();
             } else {
                 Doc.SetInPlace(this.doc, key, value, false) || true;
                 Doc.SetInPlace(this.doc, "editTitle", undefined, false);
             }
         })}
-        OnFillDown={undoBatch((value: string) => {
-            Doc.SetInPlace(this.doc, key, value, false);
-            const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
-            Doc.SetInPlace(this.doc, "editTitle", undefined, false);
-            Doc.SetInPlace(doc, "editTitle", "*", false);
-            const added = this.props.addDocument(doc);
-            doc.context = this.props.treeView.Document;
-            return added;
-        })}
         onClick={() => {
             SelectionManager.DeselectAll();
             Doc.UserDoc().activeSelection = new List([this.doc]);
@@ -240,8 +249,13 @@ class TreeView extends React.Component<TreeViewProps> {
             const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
             let addDoc = parentAddDoc;
             if (inside) {
+                const localAdd = (doc: Doc) => {
+                    const added = Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+                    added && (doc.context = this.doc.context);
+                    return added;
+                }
                 addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce(
-                    (flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc), true) || parentAddDoc(doc);
+                    (flg: boolean, doc) => flg && localAdd(doc), true) || parentAddDoc(doc);
             }
             const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument;
             return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false);
@@ -290,8 +304,12 @@ class TreeView extends React.Component<TreeViewProps> {
 
             if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) {
                 const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
-                const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce(
-                    (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true);
+                const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
+                    const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+                    added && (doc.context = this.doc.context);
+                    return added;
+                }
+                const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents),
                     this.props.treeView, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
@@ -328,18 +346,23 @@ class TreeView extends React.Component<TreeViewProps> {
 
     rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20);
     rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+    rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), 20);
 
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
         if (["links", "annotations", this.fieldKey].includes(expandKey)) {
             const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
-            const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) =>
-                (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true);
+            const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
+                const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true);
+                added && (doc.context = this.doc.context);
+                return added;
+            }
+            const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
             const docs = expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs;
             const sortKey = `${this.fieldKey}-sortAscending`;
             return <ul key={expandKey + "more"} className={this.doc.treeViewHideTitle ? "no-indent" : ""} onClick={(e) => {
-                !this.props.treeView.Document.treeViewOutlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)));
+                !this.outlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)));
                 e.stopPropagation();
             }}>
                 {!docs ? (null) :
@@ -416,7 +439,7 @@ class TreeView extends React.Component<TreeViewProps> {
             title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
             onClick={this.bulletClick}
             style={{ opacity: NumCast(this.doc.opacity, 1) }}>
-            {<FontAwesomeIcon icon={"circle"} />}
+            {(this.doc.text as RichTextField)?.Text ? <FontAwesomeIcon icon={this.childDocs?.length && !this.treeViewOpen ? ["fas", "circle"] : ["far", "circle"]} /> : (null)}
         </div>;
     }
     @computed get renderBullet() {
@@ -442,7 +465,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
     }
     onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
-    onChildDoubleClick = () => (!this.props.treeView.props.Document.treeViewOutlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
+    onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
     /**
      * Renders the EditableView title element for placement into the tree.
      */
@@ -456,11 +479,11 @@ class TreeView extends React.Component<TreeViewProps> {
                     onPointerDown={action(() => {
                         if (this.treeViewOpen) {
                             this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView :
-                                this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
+                                this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields") :
                                     this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" :
                                         this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" :
                                             (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" :
-                                                this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
+                                                this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields");
                         }
                         this.treeViewOpen = true;
                     })}>
@@ -488,7 +511,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 PanelWidth={this.truncateTitleWidth}
                 PanelHeight={returnZero}
                 contextMenuItems={this.contextMenuItems}
-                opacity={this.props.treeView.props.Document.treeViewOutlineMode ? undefined : returnOne}
+                opacity={this.outlineMode ? undefined : returnOne}
                 renderDepth={1}
                 focus={returnTrue}
                 parentActive={returnTrue}
@@ -529,7 +552,67 @@ class TreeView extends React.Component<TreeViewProps> {
                 }
             }
         } else this._editMaxWidth = "";
-        return this.doc.treeViewHideTitle || (this.props.treeView.props.Document.treeViewOutlineMode && this.doc.type !== DocumentType.COL) ? this.renderContent :
+        return this.doc.treeViewHideTitle || (this.outlineMode) ?
+            !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? this.renderContent :
+                <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}
+                    onKeyDown={e => {
+                        e.stopPropagation();
+                        e.key === "Backspace" && this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete");
+                        e.key === "Tab" && UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab");
+                        e.key === "Enter" && UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet");
+                        e.key === "Tab" && setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+                    }}
+                >
+                    <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
+                        if (this.props.active(true)) {
+                            e.stopPropagation();
+                            e.preventDefault();
+                        }
+                    }}
+                        onPointerDown={e => {
+                            if (this.props.active(true)) {
+                                e.stopPropagation();
+                                e.preventDefault();
+                            }
+                        }}
+                        onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                        {this.outlineMode ? this.renderOutlineBullet : this.renderBullet}
+                        <div ref={this._dref} style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]}>
+                            <ContentFittingDocumentView
+                                Document={this.doc}
+                                DataDoc={undefined}
+                                LayoutTemplateString={FormattedTextBox.LayoutString("text")}
+                                LibraryPath={emptyPath}
+                                renderDepth={this.props.renderDepth + 1}
+                                rootSelected={returnTrue}
+                                treeViewDoc={undefined}
+                                backgroundColor={this.props.backgroundColor}
+                                fitToBox={this.boundsOfCollectionDocument !== undefined}
+                                PanelWidth={this.rtfWidth}
+                                PanelHeight={this.rtfOutlineHeight}
+                                focus={returnFalse}
+                                ScreenToLocalTransform={this.docTransform}
+                                docFilters={returnEmptyFilter}
+                                searchFilterDocs={returnEmptyDoclist}
+                                ContainingCollectionDoc={this.props.containingCollection}
+                                ContainingCollectionView={undefined}
+                                addDocument={this.props.addDocument}
+                                moveDocument={this.move}
+                                removeDocument={this.props.removeDoc}
+                                parentActive={this.props.active}
+                                whenActiveChanged={this.props.whenActiveChanged}
+                                addDocTab={this.props.addDocTab}
+                                pinToPres={this.props.pinToPres}
+                                bringToFront={returnFalse}
+                                ContentScaling={returnOne}
+                            />
+                        </div>
+                    </div>
+
+                    <div className={`treeViewItem-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
+                        {!this.treeViewOpen ? (null) : this.renderContent}
+                    </div>
+                </div> :
             <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}>
                 <li className="collection-child">
                     <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
@@ -546,11 +629,11 @@ class TreeView extends React.Component<TreeViewProps> {
                             }
                         }}
                         onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
-                        {this.props.treeView.props.Document.treeViewOutlineMode ? this.renderOutlineBullet : this.renderBullet}
+                        {this.outlineMode ? this.renderOutlineBullet : this.renderBullet}
                         {this.renderTitle}
                     </div>
-                    <div className={`treeViewItem-border${this.props.treeView.props.Document.treeViewOutlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
-                        {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent}
+                    <div className={`treeViewItem-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
+                        {!this.treeViewOpen ? (null) : this.renderContent}
                     </div>
                 </li>
             </div>;
@@ -639,9 +722,10 @@ class TreeView extends React.Component<TreeViewProps> {
                     const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1];
                     const fieldKey = fieldKeysub.split("\'")[1];
                     if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
+                        remove(child);
+                        FormattedTextBox.SelectOnLoad = child[Id];
                         Doc.AddDocToList(docs[i - 1], fieldKey, child);
                         docs[i - 1].treeViewOpen = true;
-                        remove(child);
                         child.context = treeView.Document;
                     }
                 }
@@ -650,9 +734,10 @@ class TreeView extends React.Component<TreeViewProps> {
                 if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
                     const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1];
                     const fieldKey = fieldKeysub.split("\'")[1];
+                    remove(child);
+                    FormattedTextBox.SelectOnLoad = child[Id];
                     Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false);
                     parentCollectionDoc.treeViewOpen = true;
-                    remove(child);
                     child.context = treeView.Document;
                 }
             };
@@ -713,6 +798,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     private _mainEle?: HTMLDivElement;
 
     public _uniqueId = Utils.GenerateGuid();
+    _isChildActive = false;
     @computed get doc() { return this.props.Document; }
     @computed get dataDoc() { return this.props.DataDoc || this.doc; }
 
@@ -743,6 +829,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         const targetDataDoc = this.doc[DataSym];
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
+        SelectionManager.DeselectAll();
         if (result.length !== value.length) {
             targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
             return true;
@@ -785,45 +872,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             layoutItems.push({ description: (this.doc.treeViewHideLinkLines ? "Show" : "Hide") + " Link Lines", event: () => this.doc.treeViewHideLinkLines = !this.doc.treeViewHideLinkLines, icon: "paint-brush" });
             ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
         }
-        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({
-            description: "Buxton Layout", icon: "eye", event: () => {
-                const { ImageDocument, PdfDocument } = Docs.Create;
-                const { Document } = this.props;
-                const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
-                const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null);
-                const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
-                heroView.proto!.layout = ImageBox.LayoutString("hero");
-                heroView._showTitle = "title";
-                heroView._showTitleHover = "titlehover";
-
-                const fallback = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 });  // replace with desired double click target
-                let pdfContent: string;
-                this.childDocs?.map(d => {
-                    DocListCast(d.data).map((img, i) => {
-                        const caption = (d.captions as any)[i];
-                        if (caption) {
-                            Doc.GetProto(img).caption = caption;
-                            Doc.GetProto(img).doubleClickView = (pdfContent = StrCast(img.additionalMedia_pdfs)) ? PdfDocument(pdfContent, { title: pdfContent }) : fallback;
-                        }
-                    });
-                    Doc.GetProto(d).type = "buxton";
-                    Doc.GetProto(d).proto = heroView; // all devices "are" heroViews that share the same layout & defaults. Seems better than making them all be independent and copy a layout string  // .layout = ImageBox.LayoutString("hero");
-                });
-
-                const iconBuxtonView = ImageDocument(fallbackImg, { title: "hero", _width: 60, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") });
-                iconBuxtonView.isTemplateDoc = makeTemplate(iconBuxtonView, true, "icon_buxton");
-                Doc.UserDoc()["template-icon-view-buxton"] = new PrefetchProxy(iconBuxtonView);
-                const tempIcons = Doc.GetProto(Cast(Doc.UserDoc()["template-icons"], Doc, null));
-                Doc.AddDocToList(tempIcons, "data", iconBuxtonView);
-
-                Document.childLayoutTemplate = heroView;
-                Document.childClickedOpenTemplateView = new PrefetchProxy(detailView);
-                Document._viewType = CollectionViewType.Time;
-                Document.forceActive = true;
-                Document._pivotField = "company";
-                Document.childDropAction = "alias";
-            }
-        });
         const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({
@@ -846,13 +894,74 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         </div >;
     }
 
-    onChildClick = () => {
-        return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
+    @undoBatch
+    makeTextCollection = action((childDocs: Doc[]) => {
+        Doc.SetInPlace(this.doc, "editTitle", undefined, false);
+        const bullet = TreeView.makeTextBullet();
+        bullet.context = this.doc;
+        this.addDoc(bullet, childDocs.length ? childDocs[0] : undefined, true);
+        setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+    })
+
+    editableTitle(childDocs: Doc[]) {
+        return <EditableView
+            contents={this.dataDoc.title}
+            editing={false}
+            display={"block"}
+            maxHeight={72}
+            height={"auto"}
+            GetValue={() => StrCast(this.dataDoc.title)}
+            SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
+                if (this.props.Document.treeViewOutlineMode && enter) {
+                    this.makeTextCollection(childDocs)
+                }
+                return Doc.SetInPlace(this.dataDoc, "title", value, false);
+            })} />;
     }
-    _isChildActive = false;
-    whenActiveChanged = (isActive: boolean) => {
-        this.props.whenActiveChanged(this._isChildActive = isActive);
+
+
+    rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.PanelWidth() - 20);
+    rtfOutlineHeight = () => Math.min(this.layoutDoc?.[HeightSym](), (StrCast(this.layoutDoc?._fontSize) ? Number(StrCast(this.layoutDoc?._fontSize, "32px").replace("px", "")) : NumCast(this.layoutDoc?._fontSize)) * 2);
+    titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20));
+    documentTitle = (childDocs: Doc[]) => {
+        return <div style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]}
+            onKeyDown={e => {
+                e.stopPropagation();
+                e.key === "Enter" && this.makeTextCollection(childDocs)
+            }}>
+            <ContentFittingDocumentView
+                Document={this.doc}
+                DataDoc={undefined}
+                LayoutTemplateString={FormattedTextBox.LayoutString("text")}
+                LibraryPath={emptyPath}
+                renderDepth={this.props.renderDepth + 1}
+                rootSelected={returnTrue}
+                treeViewDoc={undefined}
+                dontRegisterView={true}
+                backgroundColor={this.props.backgroundColor}
+                PanelWidth={this.rtfWidth}
+                PanelHeight={this.rtfOutlineHeight}
+                focus={returnFalse}
+                ScreenToLocalTransform={this.titleTransform}
+                docFilters={returnEmptyFilter}
+                searchFilterDocs={returnEmptyDoclist}
+                ContainingCollectionDoc={this.doc}
+                ContainingCollectionView={this.props.CollectionView}
+                addDocument={this.props.addDocument}
+                moveDocument={returnFalse}
+                removeDocument={returnFalse}
+                parentActive={this.props.active}
+                whenActiveChanged={this.props.whenActiveChanged}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+            />
+        </div>
     }
+
+    onChildClick = () => { return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); }
+    whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }
     active = (outsideReaction: boolean | undefined) => this.props.active(outsideReaction) || this._isChildActive;
     render() {
         TraceMobx();
@@ -882,21 +991,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                     onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                     onDrop={this.onTreeDrop}
                     ref={this.createTreeDropTarget}>
-                    {hideTitle ? (null) : <EditableView
-                        contents={this.dataDoc.title}
-                        editing={false}
-                        display={"block"}
-                        maxHeight={72}
-                        height={"auto"}
-                        GetValue={() => StrCast(this.dataDoc.title)}
-                        SetValue={undoBatch((value: string) => {
-                            if (this.props.Document.treeViewOutlineMode) {
-                                const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
-                                Doc.SetInPlace(doc, "editTitle", "*", false);
-                                this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
-                            }
-                            return Doc.SetInPlace(this.dataDoc, "title", value, false);
-                        })} />}
+                    {hideTitle ? (null) : (this.doc.treeViewOutlineMode ? this.documentTitle : this.editableTitle)(childDocs)}
                     {this.doc.allowClear ? this.renderClearButton : (null)}
                     <ul className="no-indent" style={{ width: "max-content" }} > {childElements} </ul>
                 </div >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7b7c3578b..4b740d3de 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,6 +40,7 @@ import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { RichTextField } from '../../../fields/RichTextField';
 
 export type DocFocusFunc = () => boolean;
 
@@ -269,6 +270,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onKeyDown = (e: React.KeyboardEvent) => {
+        if (this.rootDoc._singleLine && ((e.key === "Backspace" && this.dataDoc.text && !(this.dataDoc.text as RichTextField)?.Text) || ["Tab", "Enter"].includes(e.key))) {
+            return;
+        }
         if (e.altKey && !(e.nativeEvent as any).StopPropagationForReal) {
             (e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work...
             e.stopPropagation();
@@ -624,6 +628,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
+        if (this.props.LayoutTemplateString) return;
         if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
             if ((e.target as any)?.closest?.("*.lm_content")) {
                 alert("You can't perform this move most likely because you don't have permission to modify the destination.");
@@ -901,7 +906,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {/* {this.allAnchors} */}
-            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) :
+            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || (!this.isSelected() && this.layoutDoc.hideLinkButton) || this.props.dontRegisterView ? (null) :
                 <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
         </div>
         );
@@ -1045,7 +1050,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
         const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
         const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
-        const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
+        const fullDegree = 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 borderRounding = this.layoutDoc.borderRounding;
         const localScale = fullDegree;
         const highlightColors = CurrentUserUtils.ActiveDashboard?.darkScheme ?
@@ -1059,8 +1064,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-            onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })}
-            onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
+            onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+            onPointerLeave={action(e => {
                 let entered = false;
                 const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
                 for (let child: any = target; child; child = child?.parentElement) {
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 79f1b7ddc..b23dffe80 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1129,13 +1129,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
         }
         let doc = undefined;
-        const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", });
-        const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" });
-        const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" });
-        const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" });
-        const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" });
-        const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" });
-        const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" });
+        const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24px", });
+        const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16px" });
+        const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20px" });
+        const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24px" });
+        const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14px" });
+        const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14px" });
+        const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14px" });
         switch (layout) {
             case 'blank':
                 doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y });
@@ -1227,8 +1227,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
         let type: string = '';
+        const effectiveType = targetDoc.treeViewOutlineMode ? DocumentType.COL : targetDoc.type;// bcz: Argh .. .need a better way to identify a slide doc
         if (activeItem) {
-            switch (targetDoc.type) {
+            switch (effectiveType) {
                 case DocumentType.PDF: type = "PDF"; break;
                 case DocumentType.RTF: type = "Text node"; break;
                 case DocumentType.COL: type = "Collection"; break;
@@ -1248,8 +1249,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
 
 
     @computed get progressivizeDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
+        const activeItem = this.activeItem;
+        const targetDoc = this.targetDoc;
+        const effectiveType = targetDoc.treeViewOutlineMode ? DocumentType.COL : targetDoc.type;
         if (activeItem && targetDoc) {
             const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black";
             const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black";
@@ -1258,7 +1260,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                     <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                         <div className="ribbon-box">
                             {this.stringType} selected
-                            <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc.type === DocumentType.COL ? "inline-flex" : "none" }}>
+                            <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: effectiveType === DocumentType.COL ? "inline-flex" : "none" }}>
                                 <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Contents</div>
                                 <div className="ribbon-button" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div>
                             </div>
@@ -1274,11 +1276,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                                 </div>
                             </div>
                             {this.viewedColorPicker}
-                            {/* <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}>
+                            {/* <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (effectiveType === DocumentType.COL && targetDoc._viewType === 'freeform') || effectiveType === DocumentType.IMG ? "inline-flex" : "none" }}>
                                 <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Zoom</div>
                                 <div className="ribbon-button" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Edit</div>
                             </div> */}
-                            <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
+                            <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || effectiveType === DocumentType.PDF || effectiveType === DocumentType.WEB || effectiveType === DocumentType.RTF ? "inline-flex" : "none" }}>
                                 <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll</div>
                                 <div className="ribbon-button" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div>
                             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 97d023673..3be02aa92 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -988,7 +988,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (documentId) {
             exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
         }
-        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+        exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
     }
 
     updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
@@ -1171,6 +1171,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     componentWillUnmount() {
+        this.endUndoTypingBatch();
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this._editorView?.destroy();
     }
@@ -1417,7 +1418,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     _lastTimedMark: Mark | undefined = undefined;
-    onKeyPress = (e: React.KeyboardEvent) => {
+    onKeyDown = (e: React.KeyboardEvent) => {
+        // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container)
+        if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) {
+            return;
+        }
         if (e.altKey) {
             e.preventDefault();
             return;
@@ -1502,8 +1507,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
         }
-        const selPad = this.props.isSelected() ? -10 : 0;
-        const selclass = this.props.isSelected() ? "-selected" : "";
+        const selPad = this.props.isSelected() && !this.layoutDoc._singleLine ? -10 : 0;
+        const selclass = this.props.isSelected() && !this.layoutDoc._singleLine ? "-selected" : "";
         return (
             <div className={"formattedTextBox-cont"}
                 style={{
@@ -1526,7 +1531,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
                     }}
                     onContextMenu={this.specificContextMenu}
-                    onKeyDown={this.onKeyPress}
+                    onKeyDown={this.onKeyDown}
                     onFocus={this.onFocused}
                     onClick={this.onClick}
                     onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c6bacc1a8..674b63e77 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,11 +7,13 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast } from "../../../../fields/Doc";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { Id } from "../../../../fields/FieldSymbols";
 import { Docs } from "../../../documents/Documents";
 import { Utils } from "../../../../Utils";
+import { listSpec } from "../../../../fields/Schema";
+import { List } from "../../../../fields/List";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
 
@@ -45,6 +47,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         keys[key] = cmd;
     }
 
+    /// bcz; Argh!!  replace with an onEnter func that conditionally handles Enter 
+    const addTextBox = (below: boolean, force?: boolean) => {
+        if (props.LayoutTemplateString) return true;
+        const layoutDoc = props.Document;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
+        if (force || props.Document._singleLine) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
+            const newDoc = Doc.MakeCopy(originalDoc, true);
+            const dataField = originalDoc[Doc.LayoutFieldKey(newDoc)];
+            newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+            if (below) newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
+            else newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
+            Doc.GetProto(newDoc).text = undefined;
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            props.addDocument(newDoc);
+            return true;
+        }
+        return false;
+    }
+
     //History commands
     bind("Mod-z", undo);
     bind("Shift-Mod-z", redo);
@@ -66,6 +91,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        /// bcz; Argh!!  replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
+        if (props.Document._singleLine) {
+            if (!props.LayoutTemplateString) return addTextBox(false, true);
+            return true;
+        }
         const ref = state.selection;
         const range = ref.$from.blockRange(ref.$to);
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -89,6 +119,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
 
     bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        /// bcz; Argh!!  replace with a onShiftTab prop conditionally handles Tab);
+        if (props.Document._singleLine) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
 
         if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
@@ -136,47 +168,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         return tx;
     };
 
-    const addTextOnRight = (force: boolean) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (force || props.Document._singleLine) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Doc.MakeCopy(originalDoc, true);
-            newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
-            newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            Doc.GetProto(newDoc).text = undefined;
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-            return true;
-        }
-        return false;
-    };
-
     //Command to create a text document to the right of the selected textbox
-    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        return addTextOnRight(true);
-    });
+    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => addTextBox(false, true));
 
     //Command to create a text document to the bottom of the selected textbox
-    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (originalDoc instanceof Doc) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Doc.MakeCopy(originalDoc, true);
-            newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
-            newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            Doc.GetProto(newDoc).text = undefined;
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-        }
-    });
+    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => addTextBox(true, true));
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
     bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
@@ -199,7 +195,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     //command to break line
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        if (addTextOnRight(false)) return true;
+        if (addTextBox(true, false)) return true;
         const trange = state.selection.$from.blockRange(state.selection.$to);
         const path = (state.selection.$from as any).path;
         const depth = trange ? liftTarget(trange) : undefined;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 307238ea1..da515aa5a 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -78,21 +78,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         runInAction(() => this.Pinned = true);
 
         this.fontSizeOptions = [
-            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48px", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72px", command: this.changeFontSize },
             { mark: null, title: "", label: "...", command: unimplementedFunction, hidden: true },
-            { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
+            { mark: null, title: "", label: "13px", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
         ];
 
         this.fontFamilyOptions = [
@@ -177,7 +177,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         this.activeListType = this.getActiveListStyle();
         this.activeAlignment = this.getActiveAlignment();
         this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
+        this.activeFontSize = !activeSizes.length ? "13px" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
         this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "...";
         this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "...";
 
@@ -255,7 +255,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
                 marks.forEach(m => {
                     m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
                     m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color);
-                    m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+                    m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "px");
                     m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight));
                 });
             }
@@ -384,7 +384,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
                     if (!self.TextView.props.isSelected(true)) {
                         switch (mark.type) {
                             case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
-                            case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break;
+                            case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "px"); break;
                         }
                     }
                     else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
@@ -425,7 +425,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
 
     changeFontSize = (mark: Mark, view: EditorView) => {
         if ((this.view?.state.selection.$from.pos || 0) < 2) {
-            this.TextView.layoutDoc._fontSize = mark.attrs.fontSize;
+            this.TextView.layoutDoc._fontSize = mark.attrs.fontSize === Number(mark.attrs.fontSize) ? `${mark.attrs.fontSize}pt` : mark.attrs.fontSize;
         }
         const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize });
         this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 5c0505909..02f9c6268 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -90,7 +90,7 @@ export class RichTextRules {
                     textDoc.inlineTextCount = numInlines + 1;
                     const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
                     const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
-                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
+                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" });
                     textDocInline.title = inlineFieldKey; // give the annotation its own title
                     textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                     textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 08d949b5e..be62249c6 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -183,7 +183,7 @@ export class Doc extends RefField {
         this.___fields = value;
         for (const key in value) {
             const field = value[key];
-            field && (this.__fieldKeys[key] = true);
+            (field !== undefined) && (this.__fieldKeys[key] = true);
             if (!(field instanceof ObjectField)) continue;
             field[Parent] = this[Self];
             field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
@@ -875,12 +875,7 @@ export namespace Doc {
     }
 
     export function isBrushedHighlightedDegree(doc: Doc) {
-        if (Doc.IsHighlighted(doc)) {
-            return 6;
-        }
-        else {
-            return Doc.IsBrushedDegree(doc);
-        }
+        return Doc.IsHighlighted(doc) ? 6 : Doc.IsBrushedDegree(doc);
     }
 
     export class DocBrush {
-- 
cgit v1.2.3-70-g09d2


From b81ad33b746387dc785580b719866f479fbd9b30 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Sep 2020 19:37:06 -0400
Subject: fixed creating text boxes with pre-set text to select that text --
 used in treeView outlines

---
 src/client/views/collections/CollectionTreeView.tsx             | 4 ++--
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 +++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx       | 8 ++++++--
 3 files changed, 11 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7765834f1..f92fbc9e3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -176,7 +176,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 2000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 2000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -937,7 +937,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                 renderDepth={this.props.renderDepth + 1}
                 rootSelected={returnTrue}
                 treeViewDoc={undefined}
-                dontRegisterView={true}
+                //dontRegisterView={true}
                 backgroundColor={this.props.backgroundColor}
                 PanelWidth={this.rtfWidth}
                 PanelHeight={this.rtfOutlineHeight}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ebad3bf45..226d99c5b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -23,6 +23,7 @@ import { CollectionView, CollectionViewType } from "../CollectionView";
 import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
+import { Id } from "../../../../fields/FieldSymbols";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -131,8 +132,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 const slide = Doc.copyDragFactory(Doc.UserDoc().emptySlide as Doc)!;
                 slide.x = x;
                 slide.y = y;
+                FormattedTextBox.SelectOnLoad = slide[Id];
                 this.props.addDocument(slide);
-                setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false));
+                //setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false));
                 e.stopPropagation();
             } else if (!e.ctrlKey && !e.metaKey) {
                 FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3be02aa92..a5e86d672 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1147,8 +1147,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (selectOnLoad && !this.props.dontRegisterView) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
-            FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
-            FormattedTextBox.SelectOnLoadChar = "";
+            if (FormattedTextBox.SelectOnLoadChar) {
+                FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
+                FormattedTextBox.SelectOnLoadChar = "";
+            } else if (curText?.Text) {
+                selectAll(this._editorView!.state, this._editorView?.dispatch);
+            }
 
         }
         selectOnLoad && this._editorView!.focus();
-- 
cgit v1.2.3-70-g09d2


From 8f70b2f3145f962e42c5388e923c5dd1cfcfa716 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Sep 2020 21:01:06 -0400
Subject: caught exception in formatted text when nagivating to a Slide in
 presentationv iew.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a5e86d672..29d9283e7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1492,8 +1492,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
                 }, 10);
             } else {
-                this.layoutDoc._height = newHeight;
-                this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
+                try {
+                    this.layoutDoc._height = newHeight;
+                    this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
+                } catch (e) { console.log("Error in tryUpdateHeight"); }
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 93b72ab7818e48592e9d42d092fa91b4ebedd49e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 12 Sep 2020 16:22:09 -0400
Subject: fixed sidebar views in text boxes to work in treeview outlines. fixed
 context menus to select docs again.  fixed linkAnchors to show up when
 isLinkButton is true but doc is selcted.   changed nudging to be ignore in
 annotation overlays

---
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/views/PreviewCursor.tsx                 |  2 +-
 .../views/collections/CollectionTreeView.scss      | 19 ++++++++
 .../views/collections/CollectionTreeView.tsx       | 21 +++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 .../nodes/formattedText/FormattedTextBox.scss      | 30 +++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 51 +++++++++++-----------
 9 files changed, 68 insertions(+), 72 deletions(-)

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

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 730db4f29..7795a4a59 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -73,8 +73,8 @@ export namespace SelectionManager {
     // computed functions, such as used in IsSelected generate errors if they're called outside of a
     // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
     // to avoid unnecessary mobx invalidations when running inside a reaction.
-    export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean {
-        return outsideReaction ?
+    export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean {
+        return !doc ? false : outsideReaction ?
             manager.SelectedDocuments.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
             computedFn(function isSelected(doc: DocumentView) {  // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
                 return manager.SelectedDocuments.get(doc) ? true : false;
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 0a4d46c1f..7575aa430 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -22,7 +22,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addDocument: (doc: Doc | Doc[]) => void;
     static _addLiveTextDoc: (doc: Doc) => void;
-    static _nudge: (x: number, y: number) => boolean;
+    static _nudge?: (x: number, y: number) => boolean;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 09a46c30f..feeb04134 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -103,6 +103,15 @@
     padding-bottom: 2px;
 }
 
+.treeViewItem-container-active {
+    z-index: 100;
+    position: relative;;
+    .formattedTextbox-sidebar {
+        background-color: #ffff001f !important;
+        height: 500px !important;
+    }
+}
+
 .treeViewItem-openRight {
     display: none;
     height: 17px;
@@ -128,6 +137,16 @@
     border: transparent 1px solid;
     display: flex;
     align-items: center;
+            ::-webkit-scrollbar {
+            display: none;
+    }
+    .formattedTextBox-cont {
+        .formattedTextbox-sidebar {
+            overflow: visible !important;
+            border-left: unset;
+        }
+        overflow: visible !important;
+    }
 
     .editableView-container-editing-oneLine {
         min-width: 15px;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 52e3c86b8..276e0b873 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -32,6 +32,7 @@ import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextField } from '../../../fields/RichTextField';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentManager } from '../../util/DocumentManager';
 
 export interface TreeViewProps {
     document: Doc;
@@ -176,7 +177,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 2000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 500, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -552,9 +553,10 @@ class TreeView extends React.Component<TreeViewProps> {
                 }
             }
         } else this._editMaxWidth = "";
+        const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc));
         return this.doc.treeViewHideTitle || (this.outlineMode) ?
             !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? this.renderContent :
-                <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}
+                <div className={`treeViewItem-container${selected ? "-active" : ""}`} ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}
                     onKeyDown={e => {
                         e.stopPropagation();
                         e.key === "Backspace" && this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete");
@@ -563,18 +565,9 @@ class TreeView extends React.Component<TreeViewProps> {
                         e.key === "Tab" && setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
                     }}
                 >
-                    <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
-                        if (this.props.active(true)) {
-                            e.stopPropagation();
-                            e.preventDefault();
-                        }
-                    }}
-                        onPointerDown={e => {
-                            if (this.props.active(true)) {
-                                e.stopPropagation();
-                                e.preventDefault();
-                            }
-                        }}
+                    <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }}
+                        onClick={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }}
+                        onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }}
                         onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                         {this.outlineMode ? this.renderOutlineBullet : this.renderBullet}
                         <div ref={this._dref} style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 543798b7e..843314c03 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -792,7 +792,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.layoutDoc._lockedTransform || this.props.Document.inOverlay) return;
+        if (this.layoutDoc._lockedTransform || this.props.Document.inOverlay || this.props.Document.treeViewOutlineMode) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -1369,7 +1369,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     _nudgeTime = 0;
     nudge = action((x: number, y: number) => {
-        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
+            this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
             this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
                 NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
             this._nudgeTime = Date.now();
@@ -1381,7 +1382,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get marqueeView() {
         return <MarqueeView
             {...this.props}
-            nudge={this.nudge}
+            nudge={this.isAnnotationOverlay ? undefined : this.nudge}
             addDocTab={this.addDocTab}
             activeDocuments={this.getActiveDocuments}
             selectDocuments={this.selectDocuments}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b9795ff37..ec1ddd0a5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -32,7 +32,7 @@ interface MarqueeViewProps {
     selectDocuments: (docs: Doc[]) => void;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
-    nudge: (x: number, y: number) => boolean;
+    nudge?: (x: number, y: number) => boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 147745d93..b372f3691 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -835,7 +835,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 e?.stopPropagation(); // DocumentViews should stop propagation of this event
             }
             cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
-            this.isSelected(true) && SelectionManager.SelectDoc(this, false);
+            !this.isSelected(true) && SelectionManager.SelectDoc(this, false);
         });
     }
 
@@ -912,7 +912,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {/* {this.allAnchors} */}
-            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || (!this.isSelected() && this.layoutDoc.hideLinkButton) || this.props.dontRegisterView ? (null) :
+            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || (!this.isSelected() && (this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton)) || this.props.dontRegisterView ? (null) :
                 <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
         </div>
         );
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 160f4ba72..847b1bb30 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -45,10 +45,11 @@
     width: 100%;
     height: 100%;
 }
-
+    
 .formattedTextBox-sidebar-handle {
     position: absolute;
-    top: calc(50% - 17.5px);
+    top: 0;
+    //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
     width: 10px;
     height: 35px;
     background: lightgray;
@@ -56,11 +57,6 @@
     cursor:grabbing;
 }
 
-.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
-    right: 0;
-    left: unset;
-}
-
 .formattedTextBox-sidebar,
 .formattedTextBox-sidebar-inking {
     border-left: dashed 1px black;
@@ -73,11 +69,6 @@
     .collectionfreeformview-container {
         position: relative;
     }
-
-    >.formattedTextBox-sidebar-handle {
-        right: unset;
-        left: -5;
-    }
 }
 
 .formattedTextBox-sidebar-inking {
@@ -396,10 +387,11 @@ footnote::after {
         width: 100%;
         height: 100%;
     }
-
+    
     .formattedTextBox-sidebar-handle {
         position: absolute;
-        top: calc(50% - 17.5px);
+        top: 0;
+        //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
         width: 10px;
         height: 35px;
         background: lightgray;
@@ -407,11 +399,6 @@ footnote::after {
         cursor:grabbing;
     }
 
-    .formattedTextBox-cont>.formattedTextBox-sidebar-handle {
-        right: 0;
-        left: unset;
-    }
-
     .formattedTextBox-sidebar,
     .formattedTextBox-sidebar-inking {
         border-left: dashed 1px black;
@@ -423,11 +410,6 @@ footnote::after {
         .collectionfreeformview-container {
             position: relative;
         }
-
-        >.formattedTextBox-sidebar-handle {
-            right: unset;
-            left: -5;
-        }
     }
 
     .formattedTextBox-sidebar-inking {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 29d9283e7..a79e70017 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1500,7 +1500,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
-    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "20%"); }
     sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
     sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
     @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
@@ -1569,32 +1569,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             }}
                         />
                     </div>
-                    {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
-                        <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
+                    {!this.layoutDoc._showSidebar ? (null) : <>
                         <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
                             style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-                                PanelHeight={this.props.PanelHeight}
-                                PanelWidth={this.sidebarWidth}
-                                scaleField={this.annotationKey + "-scale"}
-                                annotationsKey={this.annotationKey}
-                                isAnnotationOverlay={false}
-                                focus={this.props.focus}
-                                isSelected={this.props.isSelected}
-                                select={emptyFunction}
-                                active={this.annotationsActive}
-                                ContentScaling={returnOne}
-                                whenActiveChanged={this.whenActiveChanged}
-                                removeDocument={this.removeDocument}
-                                moveDocument={this.moveDocument}
-                                addDocument={this.addDocument}
-                                CollectionView={undefined}
-                                ScreenToLocalTransform={this.sidebarScreenToLocal}
-                                renderDepth={this.props.renderDepth + 1}
-                                ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
-                            </CollectionFreeFormView>
-                            <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} />
-                        </div>}
+                            {this.sidebarWidthPercent === "0%" ? (null) :
+                                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                                    PanelHeight={this.active() ? () => 1000 : this.props.PanelHeight}
+                                    PanelWidth={this.sidebarWidth}
+                                    scaleField={this.annotationKey + "-scale"}
+                                    annotationsKey={this.annotationKey}
+                                    isAnnotationOverlay={true}
+                                    focus={this.props.focus}
+                                    isSelected={this.props.isSelected}
+                                    select={emptyFunction}
+                                    active={this.annotationsActive}
+                                    ContentScaling={returnOne}
+                                    whenActiveChanged={this.whenActiveChanged}
+                                    removeDocument={this.removeDocument}
+                                    moveDocument={this.moveDocument}
+                                    addDocument={this.addDocument}
+                                    CollectionView={undefined}
+                                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                                    renderDepth={this.props.renderDepth + 1}
+                                    ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
+                            }
+                        </div>
+                        {this.props.isSelected() ? <div className="formattedTextBox-sidebar-handle" style={{ left: `calc(100% - ${this.sidebarWidthPercent} - 5px)` }} onPointerDown={this.sidebarDown} /> : (null)}
+                    </>}
                     {!this.layoutDoc._showAudio ? (null) :
                         <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
                             <FontAwesomeIcon className="formattedTextBox-audioFont"
-- 
cgit v1.2.3-70-g09d2


From 0ddb1a572d3579469bc08469d43c6fcdb114f901 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 12 Sep 2020 23:41:31 -0400
Subject: fixed stacking view selections to have the right bounding box. fixed
 text boxes to work with headerVIew template which has multiple text boxes in
 it.

---
 src/client/util/CurrentUserUtils.ts                | 33 +++++++++++-----------
 src/client/util/DropConverter.ts                   |  2 +-
 src/client/views/Main.scss                         |  4 +++
 src/client/views/collections/CollectionMenu.tsx    |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/globalCssVariables.scss           |  4 ---
 .../nodes/formattedText/FormattedTextBox.scss      |  5 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 23 +++++++++------
 .../formattedText/ProsemirrorExampleTransfer.ts    |  2 +-
 10 files changed, 43 insertions(+), 36 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 365a0a409..a63a0ba08 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -47,7 +47,7 @@ export class CurrentUserUtils {
 
     @observable public static propertiesWidth: number = 0;
 
-    // sets up the default User Templates - slideView, queryView, descriptionView
+    // sets up the default User Templates - slideView,  headerView
     static setupUserTemplateButtons(doc: Doc) {
         // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location)
         if (doc["template-mobile-button"] === undefined) {
@@ -85,29 +85,29 @@ export class CurrentUserUtils {
             });
         }
 
-        if (doc["template-button-description"] === undefined) {
-            const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created
-            descriptionTemplate.system = true;
-            descriptionTemplate[DataSym].layout =
+        if (doc["template-button-header"] === undefined) {
+            const headerTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
+            headerTemplate.system = true;
+            headerTemplate[DataSym].layout =
                 "<div>" +
-                "    <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
+                "    <FormattedTextBox {...props} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
                 "    <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
                 "</div>";
-            (descriptionTemplate.proto as Doc).isTemplateDoc = makeTemplate(descriptionTemplate.proto as Doc, true, "descriptionView");
+            (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
 
-            doc["template-button-description"] = CurrentUserUtils.ficon({
+            doc["template-button-header"] = CurrentUserUtils.ficon({
                 onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
-                dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
-                removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize", system: true
+                dragFactory: new PrefetchProxy(headerTemplate) as any as Doc,
+                removeDropProperties: new List<string>(["dropAction"]), title: "header view", icon: "window-maximize", system: true
             });
         }
 
         if (doc["template-button-link"] === undefined) {  // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
-            const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created
+            const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
             linkTemplate.system = true;
             Doc.GetProto(linkTemplate).layout =
                 "<div>" +
-                "    <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
+                "    <FormattedTextBox {...props} dontSelectOnLoad={'true'} height='{this._headerHeight||75}px' ignoreAutoHeight={'true'} background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
                 "    <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
                 "</div>";
             (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView");
@@ -190,9 +190,9 @@ export class CurrentUserUtils {
                 onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F", system: true
             });
 
-            const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true, system: true });
-            const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true, system: true });
-            const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true, system: true });
+            const details = TextDocument("", { title: "details", _height: 200, _autoHeight: true, system: true });
+            const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 75, _autoHeight: true, system: true });
+            const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 150, _autoHeight: true, system: true });
 
             const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
             const detailedTemplate = {
@@ -253,7 +253,7 @@ export class CurrentUserUtils {
 
         const requiredTypes = [
             doc["template-button-slides"] as Doc,
-            doc["template-button-description"] as Doc,
+            doc["template-button-header"] as Doc,
             doc["template-mobile-button"] as Doc,
             doc["template-button-detail"] as Doc,
             doc["template-button-simple"] as Doc,
@@ -468,7 +468,6 @@ export class CurrentUserUtils {
             { 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, noviceMode: true },
             { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
-            { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySearch as Doc },
             { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
             { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
             { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDocHolder as Doc },
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f1848f7e5..49643b5bb 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -60,7 +60,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) {
             //dbox = Doc.MakeAlias(doc);  // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
         } else if (!doc.onDragStart && !doc.isButtonBar) {
-            const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+            const layoutDoc = doc;// doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
             if (layoutDoc.type !== DocumentType.FONTICON) {
                 !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
             }
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 97ed0a901..b1ad4868c 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,6 +1,10 @@
 @import "globalCssVariables";
 @import "nodeModuleOverrides";
 
+:root {
+    --flyoutHandleWidth: 28px;
+    --menuPanelWidth: 60px;
+}
 html,
 body {
     width: 100%;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index a58edc604..3cc041e0b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -533,7 +533,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
         if (this.selectedDoc) {
             const layoutField = Doc.LayoutField(this.selectedDoc);
             const layoutStr = this.selectedDocumentView?.props.LayoutTemplateString || StrCast(layoutField);
-            return layoutStr.includes("FormattedText") || StrCast((layoutField as Doc)?.layout).includes("FormattedText");
+            return (document.activeElement as any)?.className.includes("ProseMirror") || layoutStr.includes("FormattedText") || StrCast((layoutField as Doc)?.layout).includes("FormattedText");
         }
         else return false;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index cbc62be91..9c9dad2c9 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -187,7 +187,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         const opacity = () => this.Document._currentFrame === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
         return <ContentFittingDocumentView
             Document={doc}
-            DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+            DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
             LayoutTemplate={this.props.ChildLayoutTemplate}
             LayoutTemplateString={this.props.ChildLayoutString}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 276e0b873..47d4aad94 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -177,7 +177,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 500, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index d0c90edcb..1539d1399 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -38,10 +38,6 @@ $MINIMIZED_ICON_SIZE:25;
 $MAX_ROW_HEIGHT: 44px;
 $DFLT_IMAGE_NATIVE_DIM: 900px;
 $MENU_PANEL_WIDTH: 60px;
-:root {
-    --flyoutHandleWidth: 28px;
-    --menuPanelWidth: 60px;
-}
 
 :export {
     contextMenuZindex: $contextMenu-zindex;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 847b1bb30..43e64d2d2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -43,7 +43,7 @@
     overflow: auto;
     display: inline-block;
     width: 100%;
-    height: 100%;
+    height: unset;
 }
     
 .formattedTextBox-sidebar-handle {
@@ -51,7 +51,8 @@
     top: 0;
     //top: calc(50% - 17.5px);  // use this to center vertically -- make sure it looks okay for slide views
     width: 10px;
-    height: 35px;
+    height: 100%;
+    max-height: 35px;
     background: lightgray;
     border-radius: 20px;
     cursor:grabbing;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a79e70017..873d88f7e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -64,6 +64,7 @@ export interface FormattedTextBoxProps {
     hideOnLeave?: boolean;  // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
     xMargin?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yMargin?: number;
+    dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
 }
 export const GoogleRef = "googleDocId";
 
@@ -79,6 +80,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static Instance: FormattedTextBox;
     public ProseRef?: HTMLDivElement;
     public get EditorView() { return this._editorView; }
+    private _boxRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _editorView: Opt<EditorView>;
@@ -556,7 +558,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this.CurrentDiv.getBoundingClientRect();
-        this.layoutDoc._sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+        this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
         return false;
     }
     @undoBatch
@@ -1144,7 +1146,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
 
         const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
-        if (selectOnLoad && !this.props.dontRegisterView) {
+        if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
             if (FormattedTextBox.SelectOnLoadChar) {
@@ -1314,7 +1316,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
             if (!node && this.ProseRef) {
                 const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
-                if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+                const boundsRect = lastNode?.getBoundingClientRect();
+                if (e.clientX > boundsRect.left && e.clientX < boundsRect.right &&
+                    e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+                    this._editorView?.focus();
                     this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
                 }
             } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
@@ -1473,7 +1478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     @action
     tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current?.scrollHeight;
+        let scrollHeight = this.ProseRef?.scrollHeight || 0;
         if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1);
             if (limitHeight && scrollHeight > limitHeight) {
@@ -1493,14 +1498,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }, 10);
             } else {
                 try {
-                    this.layoutDoc._height = newHeight;
+                    const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
+                    const outer = this.rootDoc[HeightSym]() - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
+                    this.rootDoc._height = newHeight + outer;
                     this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
                 } catch (e) { console.log("Error in tryUpdateHeight"); }
             }
         }
     }
 
-    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "20%"); }
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
     sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
     @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
@@ -1516,7 +1523,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const selPad = this.props.isSelected() && !this.layoutDoc._singleLine ? -10 : 0;
         const selclass = this.props.isSelected() && !this.layoutDoc._singleLine ? "-selected" : "";
         return (
-            <div className={"formattedTextBox-cont"}
+            <div className={"formattedTextBox-cont"} ref={this._boxRef}
                 style={{
                     transform: `scale(${scale})`,
                     transformOrigin: "top left",
@@ -1594,7 +1601,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                                     ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
                             }
                         </div>
-                        {this.props.isSelected() ? <div className="formattedTextBox-sidebar-handle" style={{ left: `calc(100% - ${this.sidebarWidthPercent} - 5px)` }} onPointerDown={this.sidebarDown} /> : (null)}
+                        {this.props.isSelected() ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)}
                     </>}
                     {!this.layoutDoc._showAudio ? (null) :
                         <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 9895940d1..e77cc2692 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -49,7 +49,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     /// bcz; Argh!!  replace with an onEnter func that conditionally handles Enter 
     const addTextBox = (below: boolean, force?: boolean) => {
-        if (props.LayoutTemplateString) return true;
+        if (props.Document.treeViewOutlineMode) return true;  // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored..
         const layoutDoc = props.Document;
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
-- 
cgit v1.2.3-70-g09d2


From bf8a62fbb1b70bc013ec2f342930f35d6108065b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 13 Sep 2020 04:29:37 -0400
Subject: fixed making delegates of custom headerViews.  fixed non-responsive
 carriage returns in textBoxes occasionally at the end of the text stream. 
 fixed upating titles on templated text boxes.   fixed setting layoutfield
 values in DashFieldViews to update the template and layout doc.

---
 src/client/util/CurrentUserUtils.ts                | 30 +++++++++-------------
 src/client/views/nodes/DocumentView.tsx            |  1 +
 .../views/nodes/formattedText/DashFieldView.tsx    | 22 +++++++++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 12 +++++----
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/fields/Doc.ts                                  | 10 ++++++++
 7 files changed, 48 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a0883fb8a..3dc16b864 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -85,23 +85,6 @@ export class CurrentUserUtils {
             });
         }
 
-        if (doc["template-button-header"] === undefined) {
-            const headerTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
-            headerTemplate.system = true;
-            headerTemplate[DataSym].layout =
-                "<div>" +
-                "    <FormattedTextBox {...props} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
-                "    <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
-                "</div>";
-            (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
-
-            doc["template-button-header"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
-                dragFactory: new PrefetchProxy(headerTemplate) as any as Doc,
-                removeDropProperties: new List<string>(["dropAction"]), title: "header view", icon: "window-maximize", system: true
-            });
-        }
-
         if (doc["template-button-link"] === undefined) {  // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
             const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
             linkTemplate.system = true;
@@ -253,7 +236,6 @@ export class CurrentUserUtils {
 
         const requiredTypes = [
             doc["template-button-slides"] as Doc,
-            doc["template-button-header"] as Doc,
             doc["template-mobile-button"] as Doc,
             doc["template-button-detail"] as Doc,
             doc["template-button-simple"] as Doc,
@@ -426,6 +408,17 @@ export class CurrentUserUtils {
             FormattedTextBox.SelectOnLoad = textDoc[Id];
             doc.emptySlide = textDoc;
         }
+        if (doc.emptyHeader === undefined) {
+            const headerTemplate = Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
+            headerTemplate[DataSym].layout =
+                "<div>" +
+                "    <FormattedTextBox {...props} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
+                "    <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
+                "</div>";
+            (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
+            doc.emptyHeader = headerTemplate;
+            ((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"]) });
         }
@@ -471,6 +464,7 @@ export class CurrentUserUtils {
             { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
             { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
             { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDocHolder as Doc },
+            { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc, noviceMode: true },
             { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
         ];
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b372f3691..ad99001dd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -987,6 +987,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     yMargin={10}
                     xMargin={10}
                     hideOnLeave={true}
+                    dontRegisterView={true}
                     LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
                     ContentScaling={returnOne}
                     ChromeHeight={this.chromeHeight}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f14a57e31..0332bb4fd 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,5 +1,5 @@
 import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
-import { Doc, DocListCast, Field } from "../../../../fields/Doc";
+import { Doc, DocListCast, Field, LayoutSym } from "../../../../fields/Doc";
 import { List } from "../../../../fields/List";
 import { listSpec } from "../../../../fields/Schema";
 import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
@@ -70,7 +70,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             DocServer.GetRefField(this.props.docid).
                 then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
         } else {
-            this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc;
+            this._dashDoc = this.props.tbox.rootDoc;
         }
     }
     componentWillUnmount() {
@@ -92,7 +92,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 return <input
                     className="dashFieldView-fieldCheck"
                     type="checkbox" checked={boolVal}
-                    onChange={e => this._dashDoc![this._fieldKey] = e.target.checked}
+                    onChange={e => {
+                        if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
+                        this._dashDoc![this._fieldKey] = e.target.checked;
+                    }}
                 />;
             }
             else // field value is a string, so display it as an editable span
@@ -159,9 +162,16 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 } else if (nodeText.startsWith("=:=")) {
                     Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
                 } else {
-                    const splits = newText.split(this.multiValueDelimeter);
-                    if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
-                        this._dashDoc![this._fieldKey] = splits.length > 1 ? new List<string>(splits) : newText;
+                    if (Number(newText).toString() === newText) {
+                        if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
+                        this._dashDoc![this._fieldKey] = Number(newText);
+                    } else {
+                        const splits = newText.split(this.multiValueDelimeter);
+                        if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+                            const strVal = splits.length > 1 ? new List<string>(splits) : newText;
+                            if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
+                            this._dashDoc![this._fieldKey] = strVal;
+                        }
                     }
                 }
             });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 873d88f7e..4cc0309eb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -348,7 +348,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     updateTitle = () => {
-        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+        if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
             Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey) {
             let node = this._editorView.state.doc;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index e77cc2692..f1a0188c5 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -217,11 +217,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                 const fromattrs = state.selection.$from.node().attrs;
                 if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
                     const tonode = tx3.selection.$to.node();
-                    const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
-                    splitMetadata(marks, tx4);
-                    if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
-                        dispatch(tx4);
-                    }
+                    if (tx3.doc.nodeAt(tx3.selection.to - 1)) {
+                        const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+                        splitMetadata(marks, tx4);
+                        if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
+                            dispatch(tx4);
+                        }
+                    } else dispatch(tx3.insertText("\r"));
                 })) {
                     return false;
                 }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 02f9c6268..06c73265a 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -324,7 +324,7 @@ export class RichTextRules {
                     this.Document[DataSym]["#" + tag] = "#" + tag;
                     const tags = StrCast(this.Document.tags, ":");
                     if (!tags.includes(`#${tag}:`)) {
-                        this.Document[DataSym].tags = `"${tags + "#" + tag + ':'}"`;
+                        this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`;
                     }
                     const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index be62249c6..9553c4c7b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -782,6 +782,7 @@ export namespace Doc {
         if (doc) {
             const delegate = new Doc(id, true);
             delegate.proto = doc;
+            delegate.isPrototype = true;
             delegate.author = Doc.CurrentUserEmail;
             title && (delegate.title = title);
             return delegate;
@@ -1134,6 +1135,14 @@ export namespace Doc {
         }
         return ndoc;
     }
+    export function delegateDragFactory(dragFactory: Doc) {
+        const ndoc = Doc.MakeDelegate(dragFactory);
+        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+            Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
+        }
+        return ndoc;
+    }
 
 
     export namespace Get {
@@ -1284,6 +1293,7 @@ Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTempla
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
+Scripting.addGlobal(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
 Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
 Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
-- 
cgit v1.2.3-70-g09d2


From c02f00aaece877c515d2fbca850b9dc312d62112 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 13 Sep 2020 12:32:27 -0400
Subject: added back in menu option for hiding link button.  fixed resetting
 text back to template

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ad99001dd..bcf59f74a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -763,7 +763,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const appearance = cm.findByDescription("UI Controls...");
         const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
         templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
-        //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+        DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
         !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
 
         const options = cm.findByDescription("Options...");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4cc0309eb..b09ea0374 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -250,7 +250,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
-                    if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+                    if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
                         if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
                             if (!this._pause && !this.layoutDoc._timeStampOnEnter) {
                                 timeStamp = setTimeout(() => this.pause(), 10 * 1000); // 10 seconds delay for time stamp
@@ -264,7 +264,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
                             this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
-                            this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                            this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
                             ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
                             unchanged = false;
 
-- 
cgit v1.2.3-70-g09d2


From e02715b188a8fbaf47b5aef9e1bc892bf57627a6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 13 Sep 2020 23:15:20 -0400
Subject: got rid of textTemplates which were superfluous.  fixed problems with
 stored Marks on templates when creating one by typing and the template is the
 default layout.

---
 src/client/util/DropConverter.ts                   |  6 +----
 .../views/nodes/formattedText/DashDocView.tsx      |  8 -------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 26 +++++++++++++---------
 .../views/nodes/formattedText/RichTextSchema.tsx   | 11 ---------
 src/fields/Doc.ts                                  | 14 ++++--------
 5 files changed, 21 insertions(+), 44 deletions(-)

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

diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 49643b5bb..1bbd46938 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -37,11 +37,7 @@ export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string
         }
     });
     if (first) {
-        if (docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template, but we still want its caption to be a textTemplate
-            if (doc.caption instanceof RichTextField && !doc.caption.Empty()) {
-                doc["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`);
-            }
-        } else {
+        if (!docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template
             any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any;
         }
     }
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 90655bad6..ffa6e904a 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -132,14 +132,6 @@ export class DashDocView extends React.Component<IDashDocView> {
                 if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
                     finalLayout.rootDocument = dashDoc.aliasOf;
                 }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
                 this._finalLayout = finalLayout;
                 this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null);
                 return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b09ea0374..668b84903 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -238,20 +238,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
             const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-            const curTemp = Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField);               // the actual text in the text box
+            const curTemp = Cast(this.layoutDoc[this.props.fieldKey], RichTextField);               // the actual text in the text box
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
             let unchanged = true;
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
 
+            const removeSelection = (json: string | undefined) => {
+                return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
+            }
 
             if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
-                if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
+                if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
                     if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
+                        if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
                             if (!this._pause && !this.layoutDoc._timeStampOnEnter) {
                                 timeStamp = setTimeout(() => this.pause(), 10 * 1000); // 10 seconds delay for time stamp
                             }
@@ -824,10 +827,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.editorState = reaction(
             () => {
                 if (!this.dataDoc || !this.layoutDoc) return undefined;
-                if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
+                if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) {
                     return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
                 }
-                return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
+                return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data;
             },
             incomingValue => {
                 if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
@@ -837,7 +840,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.tryUpdateHeight();
                     }
                 }
-            }
+            },
         );
         this._disposers.pullDoc = reaction(
             () => this.props.Document[Pulls],
@@ -1109,8 +1112,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     private setupEditor(config: any, fieldKey: string) {
         const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
-        const useTemplate = !curText?.Text && this.layoutDoc[this.props.fieldKey + "-textTemplate"];
-        const rtfField = Cast((useTemplate && this.layoutDoc[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
+        const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
         if (this.ProseRef) {
             const self = this;
             this._editorView?.destroy();
@@ -1149,8 +1151,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
-            if (FormattedTextBox.SelectOnLoadChar) {
-                FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
+            if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
+                const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
+                const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+                const curMarks = this._editorView!.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+                const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
+                this._editorView.dispatch(this._editorView!.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar).setStoredMarks(storedMarks));
                 FormattedTextBox.SelectOnLoadChar = "";
             } else if (curText?.Text) {
                 selectAll(this._editorView!.state, this._editorView?.dispatch);
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index a80d2639d..962085f0d 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -177,17 +177,6 @@ export class DashDocView {
 
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
-                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
-                // }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
                 return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
             },
                 (res) => doReactRender(res.finalLayout, res.resolvedDataDoc),
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9553c4c7b..ff45dda80 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -507,7 +507,9 @@ export namespace Doc {
             Doc.SetLayout(alias, Doc.MakeAlias(layout));
         }
         alias.aliasOf = doc;
-        alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`);
+        if (doc !== Doc.GetProto(doc)) {
+            alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`);
+        }
         alias.author = Doc.CurrentUserEmail;
         alias[AclSym] = doc[AclSym];
 
@@ -782,7 +784,6 @@ export namespace Doc {
         if (doc) {
             const delegate = new Doc(id, true);
             delegate.proto = doc;
-            delegate.isPrototype = true;
             delegate.author = Doc.CurrentUserEmail;
             title && (delegate.title = title);
             return delegate;
@@ -841,14 +842,6 @@ export namespace Doc {
             Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
             (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
         }
-        // copy the textTemplates from 'this' (not 'self') because the layout contains the template info, not the original doc
-        if (templateCaptionValue instanceof RichTextField && !templateCaptionValue.Empty()) {
-            templateField["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`);
-        }
-        if (templateFieldValue instanceof RichTextField && !templateFieldValue.Empty()) {
-            templateField[metadataFieldKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`);
-        }
-
         // get the layout string that the template uses to specify its layout
         const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
 
@@ -1137,6 +1130,7 @@ export namespace Doc {
     }
     export function delegateDragFactory(dragFactory: Doc) {
         const ndoc = Doc.MakeDelegate(dragFactory);
+        ndoc.isPrototype = true;
         if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
             dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
             Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
-- 
cgit v1.2.3-70-g09d2


From bf5718d25e372689b97965eafc80dd7cbdd2963c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Sep 2020 11:22:22 -0400
Subject: fixed userdoc tree view layout issues caused by outline mode.  added
 treeViewHideHeader to avoid hackiness of overlaoding treeVewHideTitle

---
 src/client/documents/Documents.ts                                  | 3 ++-
 src/client/views/collections/CollectionTreeView.scss               | 4 ++--
 src/client/views/collections/CollectionTreeView.tsx                | 6 +++---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 1 +
 src/client/views/nodes/formattedText/FormattedTextBox.tsx          | 7 ++++---
 5 files changed, 12 insertions(+), 9 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8fe55b9a6..abe50e011 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -190,7 +190,8 @@ export interface DocumentOptions {
     _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
     freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
     treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
-    treeViewHideTitle?: boolean; // whether to hide the top document of a tree view
+    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.
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
     treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index feeb04134..aaf17153a 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -136,8 +136,8 @@
 .treeViewItem-header {
     border: transparent 1px solid;
     display: flex;
-    align-items: center;
-            ::-webkit-scrollbar {
+    //align-items: center;
+    ::-webkit-scrollbar {
             display: none;
     }
     .formattedTextBox-cont {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 47d4aad94..ff3c38a43 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -177,7 +177,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, treeViewHideHeader: true, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10, templates: new List<string>([Templates.Title.Layout]) });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -554,7 +554,7 @@ class TreeView extends React.Component<TreeViewProps> {
             }
         } else this._editMaxWidth = "";
         const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc));
-        return this.doc.treeViewHideTitle || (this.outlineMode) ?
+        return this.doc.treeViewHideHeader ?
             !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? this.renderContent :
                 <div className={`treeViewItem-container${selected ? "-active" : ""}`} ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}
                     onKeyDown={e => {
@@ -565,7 +565,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         e.key === "Tab" && setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
                     }}
                 >
-                    <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }}
+                    <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ alignItems: this.outlineMode ? "center" : undefined, maxWidth: this._editMaxWidth }}
                         onClick={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }}
                         onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }}
                         onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 843314c03..60e347786 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1233,6 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
+        Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
         !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 668b84903..31d471571 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -616,15 +616,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const appearance = cm.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
-        this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
+        // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
         Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         appearanceItems.push({
-            description: "Convert to be a template style", event: () => {
+            description: "Make Default Layout", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
                     this.rootDoc.title = "text";
                     this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
-                } else {
+                } else if (!this.rootDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
                     this.rootDoc.title = "text";
                     this.rootDoc.layout = (this.layoutDoc as Doc).layout as string;
@@ -640,6 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
                     }, 10);
                 }
+                Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
         });
-- 
cgit v1.2.3-70-g09d2


From a3e34ebd705778e8bcc11f745087cff2bcafbcaf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Sep 2020 14:53:20 -0400
Subject: updated the custom text box to specify frontSize through document
 values.

---
 src/client/documents/Documents.ts                         | 2 ++
 src/client/util/CurrentUserUtils.ts                       | 6 +++---
 src/client/views/DocComponent.tsx                         | 2 +-
 src/client/views/nodes/FieldView.tsx                      | 1 +
 src/client/views/nodes/formattedText/DashFieldView.scss   | 3 ++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++---
 6 files changed, 12 insertions(+), 8 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index abe50e011..0ac153d74 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -57,6 +57,8 @@ const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
 export interface DocumentOptions {
     system?: boolean;
     _autoHeight?: boolean;
+    _headerHeight?: number; // height of header of custom notes
+    _headerFontSize?: number; // font size of header of custom notes
     _panX?: number;
     _panY?: number;
     _width?: number;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 44cd1f8c0..83b924181 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -409,11 +409,11 @@ export class CurrentUserUtils {
             doc.emptySlide = textDoc;
         }
         if (doc.emptyHeader === undefined) {
-            const headerTemplate = Docs.Create.TextDocument(" ", { title: "header", target: doc, _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
+            const headerTemplate = Docs.Create.TextDocument(" ", { title: "header", target: doc, _headerHeight: 44, _headerFontSize: 9, _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
             headerTemplate[DataSym].layout =
                 "<div>" +
-                "    <FormattedTextBox {...props} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} height='{this._headerHeight||75}px' background='{this._headerColor||this.target.userColor||`orange`}' fieldKey={'header'}/>" +
-                "    <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
+                "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.userColor}' />" +
+                "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
                 "</div>";
             (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
             doc.emptyHeader = headerTemplate;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index aa888b34a..ccfc37d4e 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -104,7 +104,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
 
         styleFromLayoutString = (scale: number) => {
             const style: { [key: string]: any } = {};
-            const divKeys = ["width", "height", "background", "top", "position"];
+            const divKeys = ["width", "height", "fontSize", "left", "background", "top", "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 || "";
             };
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4ec5bc534..0073ff77f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -59,6 +59,7 @@ export interface FieldViewProps {
     ChromeHeight?: () => number;
     childLayoutTemplate?: () => Opt<Doc>;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
+    fontSize?: number;
     height?: number;
     width?: number;
     background?: string;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 23cf1e79b..09dfb349f 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -18,7 +18,7 @@
     .dashFieldView-labelSpan {
         position: relative;
         display: inline-block;
-        font-size: small;
+        font-weight: normal;
     }
     .dashFieldView-fieldSpan {
         min-width: 20px;
@@ -27,6 +27,7 @@
         position: relative;
         display: inline;
         background-color: rgba(155, 155, 155, 0.24);
+        font-weight: bold;
         span {
             min-width: 100%;
             display: inline-block;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 31d471571..c6b4775c9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1536,17 +1536,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     transformOrigin: "top left",
                     width: `${100 / scale}%`,
                     height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`,
-                    ...this.styleFromLayoutString(scale)
+                    ...this.styleFromLayoutString(scale)   // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
                 }}>
                 <div className={`formattedTextBox-cont`} ref={this._ref}
                     style={{
                         overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
                         width: "100%",
-                        height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
+                        height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined),
                         background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
                         pointerEvents: interactive ? undefined : "none",
-                        fontSize: Cast(this.layoutDoc._fontSize, "string", null),
+                        fontSize: this.props.fontSize || Cast(this.layoutDoc._fontSize, "string", null),
                         fontWeight: Cast(this.layoutDoc._fontWeight, "number", null),
                         fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
                     }}
-- 
cgit v1.2.3-70-g09d2


From 6c03a4ad74ca93311139e8fecda8a4793c103488 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Sep 2020 22:17:54 -0400
Subject: updated header text view.  fixed dragging tree view items to
 unregister listeners. fixed contextMenu icon selection to ignore
 dashFieldViews.   fixed selectOnLoadChar to not apply when a
 childlayoutstring is specified.  fixed dropping text box on item view to only
 use json parseable text.

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/CurrentUserUtils.ts                | 13 ++++++++--
 src/client/views/DocumentButtonBar.tsx             |  1 +
 src/client/views/PreviewCursor.tsx                 |  4 +--
 src/client/views/collections/CollectionMenu.tsx    |  6 +++--
 .../views/collections/CollectionTreeView.tsx       | 12 +++++++++
 src/client/views/collections/TabDocView.tsx        | 12 ++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/AudioBox.tsx                |  4 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  6 ++---
 .../nodes/formattedText/FormattedTextBox.scss      |  5 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 29 +++++++++++++++-------
 15 files changed, 73 insertions(+), 30 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d22e20101..0911f3e04 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -98,6 +98,7 @@ export interface DocumentOptions {
     layoutKey?: string;
     type?: string;
     title?: string;
+    version?: string; // version identifier for a document
     label?: string;
     hidden?: boolean;
     userDoc?: Doc; // the userDocument
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b38cdabbc..6d4035b75 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -408,7 +408,7 @@ export class CurrentUserUtils {
             FormattedTextBox.SelectOnLoad = textDoc[Id];
             doc.emptySlide = textDoc;
         }
-        if (doc.emptyHeader === undefined) {
+        if (doc.emptyHeader === undefined || (doc.emptyHeader as Doc).version !== "0") {
             const json = {
                 doc: {
                     type: "doc",
@@ -428,7 +428,7 @@ export class CurrentUserUtils {
                 selection: { type: "text", anchor: 1, head: 1 },
                 storedMarks: []
             };
-            const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { title: "header", target: doc, _headerHeight: 24, _headerFontSize: 9, _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
+            const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { title: "header", version: "0", target: doc, _height: 70, _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
             headerTemplate[DataSym].layout =
                 "<div>" +
                 "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.userColor}' />" +
@@ -1001,6 +1001,15 @@ export class CurrentUserUtils {
         doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
         doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
 
+        // uncomment this to setup a default note style that uses the custom header layout
+        PromiseValue(doc.emptyHeader).then(factory => {
+            if (Cast(doc.defaultTextLayout, Doc, null)?.version !== "0") {
+                const deleg = Doc.delegateDragFactory(factory as Doc);
+                deleg.title = "header";
+                doc.defaultTextLayout = new PrefetchProxy(deleg);
+                Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
+            }
+        });
         return doc;
     }
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e8b18fca4..7629ed173 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -298,6 +298,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         while (child.children.length) {
             const next = Array.from(child.children).find(c => typeof (c.className) === "string");
             if (next?.className.includes("documentView-node")) break;
+            if (next?.className.includes("dashFieldView")) break;
             if (next) child = next;
             else break;
         }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 7575aa430..ee5a390a7 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -22,7 +22,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addDocument: (doc: Doc | Doc[]) => void;
     static _addLiveTextDoc: (doc: Doc) => void;
-    static _nudge?: (x: number, y: number) => boolean;
+    static _nudge?: undefined | ((x: number, y: number) => boolean);
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
@@ -158,7 +158,7 @@ export class PreviewCursor extends React.Component<{}> {
         addLiveText: (doc: Doc) => void,
         getTransform: () => Transform,
         addDocument: (doc: Doc | Doc[]) => boolean,
-        nudge: (nudgeX: number, nudgeY: number) => boolean) {
+        nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean)) {
         this._clickPoint = [x, y];
         this._onKeyPress = onKeyPress;
         this._addLiveTextDoc = addLiveText;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8342c5b12..9e67faed5 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -123,8 +123,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
         params: ["target", "source"], title: "item view",
         script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
         immediate: undoBatch((source: Doc[]) => {
-            if (source.length === 1 && source[0].type === DocumentType.RTF && Cast(source[0].text, RichTextField, null)?.Text) {
-                Doc.SetInPlace(this.target, "childLayoutString", Cast(source[0].text, RichTextField, null)?.Text, false);
+            let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text;
+            try { formatStr && JSON.parse(formatStr); } catch (e) { formatStr = ""; }
+            if (source.length === 1 && formatStr) {
+                Doc.SetInPlace(this.target, "childLayoutString", formatStr, false);
             } else if (source.length) {
                 this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]);
             } else {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 60ce7e001..463439762 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -145,15 +145,25 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     componentWillUnmount() {
+        console.log("DISMOUT" + this.doc.title);
         document.removeEventListener("pointermove", this.onDragMove, true);
+        document.removeEventListener("pointermove", this.onDragUp, true);
     }
 
+    onDragUp = (e: PointerEvent) => {
+        console.log("DUP" + this.doc.title);
+        document.removeEventListener("pointerup", this.onDragUp, true);
+        document.removeEventListener("pointermove", this.onDragMove, true);
+    }
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
         if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
             this._header!.current!.className = "treeViewItem-header";
             document.removeEventListener("pointermove", this.onDragMove, true);
             document.addEventListener("pointermove", this.onDragMove, true);
+            document.removeEventListener("pointerup", this.onDragUp, true);
+            document.addEventListener("pointerup", this.onDragUp, true);
+            console.log("DSTART" + this.doc.title);
         }
     }
     onPointerLeave = (e: React.PointerEvent): void => {
@@ -161,6 +171,8 @@ class TreeView extends React.Component<TreeViewProps> {
         if (this._header?.current?.className !== "treeViewItem-header-editing") {
             this._header!.current!.className = "treeViewItem-header";
         }
+        console.log("DLEAVE" + this.doc.title);
+        document.removeEventListener("pointerup", this.onDragUp, true);
         document.removeEventListener("pointermove", this.onDragMove, true);
     }
     onDragMove = (e: PointerEvent): void => {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f4fda67cd..62f6d50fc 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -28,6 +28,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionViewType } from './CollectionView';
 import React = require("react");
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -330,9 +331,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         />
                     </div>
                 </div>}
-            <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
-                <FontAwesomeIcon icon={"globe-asia"} size="lg" />
-            </div>
+
+            <Tooltip title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
+                <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
+                    <FontAwesomeIcon icon={"globe-asia"} size="lg" />
+                </div>
+            </Tooltip>
         </>;
     }
     focusFunc = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => void) => afterFocus?.();
@@ -367,7 +371,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
-                {this._document!._viewType !== CollectionViewType.Freeform ? (null) : this.renderMiniMap()}
+                {this._document._viewType !== CollectionViewType.Freeform ? (null) : this.renderMiniMap()}
             </>;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 60e347786..ef58afeb5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1116,8 +1116,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     dataProvider={this.childDataProvider}
                     sizeProvider={this.childSizeProvider}
                     pointerEvents={this.backgroundActive || this.props.childPointerEvents ?
-                        true :
-                        (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
+                        "all" :
+                        (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
                     //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
                     fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ec1ddd0a5..6436e74e9 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -137,7 +137,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 //setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false));
                 e.stopPropagation();
             } else if (!e.ctrlKey && !e.metaKey) {
-                FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
+                FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.ChildLayoutString ? e.key : "";
                 const tbox = Docs.Create.TextDocument("", {
                     _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
                     _fontFamily: StrCast(Doc.UserDoc().fontFamily),
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 7658ee5cb..88f6a65be 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -535,7 +535,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             return <DocumentView {...this.props}
                 Document={mark}
                 focus={() => this.playLink(mark)}
-                pointerEvents={true}
+                pointerEvents={"all"}
                 rootSelected={returnFalse}
                 LayoutTemplate={undefined}
                 ContainingCollectionDoc={this.props.Document}
@@ -628,7 +628,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                                             backgroundColor={returnTransparent}
                                             ContentScaling={returnOne}
                                             forcedBackgroundColor={returnTransparent}
-                                            pointerEvents={false}
+                                            pointerEvents={"none"}
                                             LayoutTemplate={undefined}
                                             LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
                                         />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3be32dce5..5df36435c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -265,7 +265,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 zIndex: this.ZInd,
                 mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document._isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
+                pointerEvents: this.props.Document._isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents
             }} >
 
             {Doc.UserDoc().renderStyle !== "comic" ? (null) :
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 616cddfcf..2af5b3182 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -74,7 +74,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
 
     render() {
         const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
-        const childProps: DocumentViewProps = { ...this.props, pointerEvents: false, parentActive: this.props.active };
+        const childProps: DocumentViewProps = { ...this.props, pointerEvents: "none", parentActive: this.props.active };
         const clearButton = (which: string) => {
             return <div className={`clear-button ${which}`}
                 onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 771a8f526..15e571839 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -79,7 +79,7 @@ export interface DocumentViewProps {
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    pointerEvents?: boolean;
+    pointerEvents?: string;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
     parentActive: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -960,7 +960,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     dontRegisterView={false}
                     forcedBackgroundColor={returnTransparent}
                     removeDocument={this.hideLinkAnchor}
-                    pointerEvents={false}
+                    pointerEvents={"none"}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
             </div >);
@@ -1017,7 +1017,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>;
     }
     @computed get ignorePointerEvents() {
-        return this.props.pointerEvents === false ||
+        return this.props.pointerEvents === "none" ||
             (SnappingManager.GetIsDragging() && this.Document["_isBackground-canClick"]) ||
             (this.Document._isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
             (this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 43e64d2d2..d1109b388 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -86,7 +86,9 @@
 }
 
 .formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner, .formattedTextBox-inner-selected {
+.formattedTextBox-inner, 
+.formattedTextBox-inner-minimal,
+.formattedTextBox-inner-selected {
     height: 100%;
     white-space: pre-wrap;
     .ProseMirror:hover {
@@ -426,6 +428,7 @@ footnote::after {
         left: 10%;
     }
 
+    .formattedTextBox-inner-minimal,
     .formattedTextBox-inner-rounded,
     .formattedTextBox-inner {
         height: 100%;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c6b4775c9..dc7d355b7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -238,7 +238,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
             const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-            const curTemp = Cast(this.layoutDoc[this.props.fieldKey], RichTextField);               // the actual text in the text box
+            const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined;               // the actual text in the text box
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
@@ -247,7 +247,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
             const removeSelection = (json: string | undefined) => {
                 return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
-            }
+            };
 
             if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
                 if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) {
@@ -617,7 +617,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
         // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        appearanceItems.push({ description: "Reset default note style", event: () => this.rootDoc.layoutKey = "layout", icon: "eye" });
         appearanceItems.push({
             description: "Make Default Layout", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
@@ -1155,9 +1155,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
-                const curMarks = this._editorView!.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+                const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
                 const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
-                this._editorView.dispatch(this._editorView!.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar).setStoredMarks(storedMarks));
+                this._editorView.dispatch(this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar).setStoredMarks(storedMarks));
                 FormattedTextBox.SelectOnLoadChar = "";
             } else if (curText?.Text) {
                 selectAll(this._editorView!.state, this._editorView?.dispatch);
@@ -1496,7 +1496,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0);
             const dh = NumCast(this.rootDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
-            if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
+            if (!this.props.LayoutTemplateString && this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
                 // 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...
                 console.log("Delayed height adjustment...");
                 setTimeout(() => {
@@ -1505,6 +1505,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }, 10);
             } else {
                 try {
+                    // let ele = this._boxRef.current;
+                    // while (ele && ele.className !== "documentView-contentsView") ele = ele.parentElement as any;
+                    // if (ele) {
+                    //     const docHeight = Number(getComputedStyle(ele).height.replace("px", ""));
+                    //     const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
+                    //     const outer = docHeight - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
+                    //     this.rootDoc._height = newHeight + outer;
+                    //     this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
+                    // }
                     const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
                     const outer = this.rootDoc[HeightSym]() - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
                     this.rootDoc._height = newHeight + outer;
@@ -1527,7 +1536,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
         }
-        const selPad = this.props.isSelected() && !this.layoutDoc._singleLine ? -10 : 0;
+        const minimal = this.props.ignoreAutoHeight;
+        const selPad = (this.props.isSelected() && !this.layoutDoc._singleLine) || minimal ? -10 : 0;
         const selclass = this.props.isSelected() && !this.layoutDoc._singleLine ? "-selected" : "";
         return (
             <div className={"formattedTextBox-cont"} ref={this._boxRef}
@@ -1575,10 +1585,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
                         style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
-                        <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
+                        <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
-                                padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+                                padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) :
+                                    `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
                                 pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
-- 
cgit v1.2.3-70-g09d2


From 187fe40883c8698ecf9401d4ecfba68a89096f6d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 15 Sep 2020 14:48:07 -0400
Subject: updated formattedTextBox to update title when editing a template

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc7d355b7..b88d95b80 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -353,7 +353,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     updateTitle = () => {
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
-            Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey) {
+            (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
             const str = node.textContent;
-- 
cgit v1.2.3-70-g09d2


From 08c657346502d7aacbbdb6e7624b13d1a51d8f46 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 15 Sep 2020 17:50:06 -0400
Subject: rearranged menu items, made a bunch developer mode.  fixed setting
 color via ColorBox of colelctions.

---
 src/client/util/CurrentUserUtils.ts                 |  2 +-
 .../util/Import & Export/ImportMetadataEntry.tsx    |  1 -
 .../views/collections/CollectionLinearView.tsx      | 21 +++++++++------------
 src/client/views/collections/CollectionView.tsx     |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  7 ++++---
 src/client/views/nodes/ColorBox.tsx                 |  2 +-
 src/client/views/nodes/DocumentView.tsx             | 16 ++++++++++------
 src/client/views/nodes/FontIconBox.tsx              | 14 +++++---------
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  9 +++++----
 9 files changed, 35 insertions(+), 38 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2a03b9f32..891cc28fd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -503,7 +503,7 @@ export class CurrentUserUtils {
         }
         const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
         const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({
-            _nativeWidth: 50, _nativeHeight: 50, _width: 50, _height: 50,
+            _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35,
             icon,
             title,
             toolTip,
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 1870213b9..45d8c0c63 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx	
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -3,7 +3,6 @@ import { observer } from "mobx-react";
 import { EditableView } from "../../views/EditableView";
 import { action, computed } from "mobx";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faPlus } from "@fortawesome/free-solid-svg-icons";
 import { Doc } from "../../../fields/Doc";
 import { StrCast, BoolCast } from "../../../fields/Types";
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index e1c7339c9..6d7ce629b 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -1,24 +1,21 @@
+import { Tooltip } from '@material-ui/core';
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { documentSchema } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
 import { makeInterface } from '../../../fields/Schema';
-import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnOne, returnTrue, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
-import "./CollectionLinearView.scss";
-import { CollectionViewType } from './CollectionView';
-import { CollectionSubView } from './CollectionSubView';
-import { DocumentView } from '../nodes/DocumentView';
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
-import { Tooltip } from '@material-ui/core';
-import { all } from 'bluebird';
-import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import "./CollectionLinearView.scss";
+import { CollectionSubView } from './CollectionSubView';
+import { CollectionViewType } from './CollectionView';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dd754e0fe..65c97da26 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -286,7 +286,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             const existingVm = ContextMenu.Instance.findByDescription(category);
             const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
             catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
-            catItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
             !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" });
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 37d2ca303..1cae12a9d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1233,7 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
-        Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
         !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
@@ -1253,7 +1253,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.props.ContainingCollectionView &&
             optionItems.push({ description: "Move Items Out of Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
-        optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+        this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
         if (!Doc.UserDoc().noviceMode) {
             optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
             optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
@@ -1262,7 +1262,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
         const mores = ContextMenu.Instance.findByDescription("More...");
         const moreItems = mores && "subitems" in mores ? mores.subitems : [];
-        moreItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+        moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
+        moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
         !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
     }
 
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 43f6cc082..fcc9e50f5 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -40,7 +40,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
                     if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {  // this situation typically occurs when you have a link dot 
                         targetDoc.backgroundColor = Doc.UserDoc().backgroundColor;  // bcz: don't know how to change the color of an inline template...
                     }
-                    else if (RichTextMenu.Instance.TextViewFieldKey && window.getSelection()?.toString() !== "") {
+                    else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== "") {
                         Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + "-color"] = Doc.UserDoc().backgroundColor;
                     } else {
                         Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific.  'backgroundColor' would apply to all templates, but has no UI at the moment
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5aabebf55..655b64f19 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -783,7 +783,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         const appearance = cm.findByDescription("UI Controls...");
         const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
-        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
+        !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
         DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
         !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
 
@@ -793,12 +793,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (!this.Document.annotationOn) {
                 const options = cm.findByDescription("Options...");
                 const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
-                optionItems.push({ description: "Bring to Front", event: () => this.props.bringToFront(this.rootDoc, false), icon: "expand-arrows-alt" });
-                optionItems.push({ description: "Send to Back", event: () => this.props.bringToFront(this.rootDoc, true), icon: "expand-arrows-alt" });
-                optionItems.push({ description: this.rootDoc["_isBackground-canClick"] ? "Unlock from Back" : "Lock in Back", event: this.toggleLockInBack, icon: "expand-arrows-alt" });
                 !this.props.treeViewDoc && this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
                 !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
+                const zorders = cm.findByDescription("ZOrder...");
+                const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
+                zorderItems.push({ description: "Bring to Front", event: () => this.props.bringToFront(this.rootDoc, false), icon: "expand-arrows-alt" });
+                zorderItems.push({ description: "Send to Back", event: () => this.props.bringToFront(this.rootDoc, true), icon: "expand-arrows-alt" });
+                zorderItems.push({ description: this.rootDoc["_isBackground-canClick"] ? "Unlock from Back" : "Lock in Back", event: this.toggleLockInBack, icon: "expand-arrows-alt" });
+                !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: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
@@ -817,7 +821,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const funcs: ContextMenuProps[] = [];
-        if (this.layoutDoc.onDragStart) {
+        if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
@@ -844,7 +848,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const collectionAcl = GetEffectiveAcl(this.props.ContainingCollectionDoc?.[DataSym]);
-        if ((collectionAcl === AclAdmin || collectionAcl === AclEdit) && this.props.removeDocument) {
+        if (this.props.removeDocument) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
             moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
         }
 
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index a34bf64b0..463f1654e 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -41,18 +41,14 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
         dragFactory && this.props.addDocTab(dragFactory, "add:right");
     }
-    dragAsTemplate = (): void => {
-        this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
-    }
-    useAsPrototype = (): void => {
-        this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
-    }
+    dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); }
+    useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); }
 
     specificContextMenu = (): void => {
         const cm = ContextMenu.Instance;
         cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
-        cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
-        cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
+        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
+        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
     }
 
     componentWillUnmount() {
@@ -75,7 +71,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
             }}>
             <div className="menuButton-wrap">
                 {icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color}
-                    size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />}
+                    size={this.layoutDoc.iconShape === "square" ? "sm" : "sm"} />}
                 {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
                 {this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)}
             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b88d95b80..c0c3bdd80 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -586,7 +586,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }), icon: "eye"
             });
         });
-        changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
+        changeItems.push({ description: "plain", event: undoBatch(() => Doc.setNativeView(this.rootDoc)), icon: "eye" });
+        !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             highlighting.push({
@@ -617,8 +618,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
         // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        appearanceItems.push({ description: "Reset default note style", event: () => this.rootDoc.layoutKey = "layout", icon: "eye" });
-        appearanceItems.push({
+        !Doc.UserDoc().noviceMode && appearanceItems.push({ description: "Reset default note style", event: () => this.rootDoc.layoutKey = "layout", icon: "eye" });
+        !Doc.UserDoc().noviceMode && appearanceItems.push({
             description: "Make Default Layout", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
@@ -644,7 +645,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
         });
-        appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" });
+        !Doc.UserDoc().noviceMode && appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" });
         cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
         const options = cm.findByDescription("Options...");
-- 
cgit v1.2.3-70-g09d2


From 19af1ce5d8216dd5eac3df0def1c059d7d8975ec Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Sep 2020 02:58:34 -0400
Subject: changed text box headers from a template to a title bar.

---
 src/client/documents/Documents.ts                  |  4 +--
 src/client/util/CurrentUserUtils.ts                | 35 +++++++++++++++-------
 src/client/util/SettingsManager.tsx                |  6 ++--
 src/client/util/SharingManager.tsx                 |  6 ++--
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.tsx           | 18 +++++------
 src/client/views/GlobalKeyHandler.ts               |  2 +-
 src/client/views/PreviewCursor.tsx                 | 11 ++-----
 .../collections/CollectionMasonryViewFieldRow.tsx  |  2 +-
 src/client/views/collections/CollectionMenu.tsx    |  4 +--
 .../CollectionStackingViewFieldColumn.tsx          |  4 +--
 src/client/views/collections/CollectionSubView.tsx |  4 +--
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/SchemaTable.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 ++---------
 .../collectionGrid/CollectionGridView.tsx          |  2 +-
 src/client/views/nodes/AudioBox.tsx                |  8 ++---
 src/client/views/nodes/DocumentView.scss           |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 35 +++++++++++++---------
 src/client/views/nodes/WebBox.tsx                  | 11 ++-----
 .../views/nodes/formattedText/DashFieldView.tsx    |  4 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  7 +++--
 src/client/views/pdf/PDFViewer.tsx                 |  7 +++--
 24 files changed, 97 insertions(+), 100 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 36834d2df..586c5f67c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1012,7 +1012,7 @@ export namespace DocUtils {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
             layout = CollectionView.LayoutString;
         } else {
-            created = Docs.Create.TextDocument("", { ...{ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 25, _autoHeight: true }, ...resolved });
+            created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
             layout = FormattedTextBox.LayoutString;
         }
         if (created) {
@@ -1072,7 +1072,7 @@ export namespace DocUtils {
                 description: ":" + StrCast(note.title),
                 event: undoBatch((args: { x: number, y: number }) => {
                     const textDoc = Docs.Create.TextDocument("", {
-                        _width: 200, x, y, _autoHeight: note._autoHeight !== false, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
+                        _width: 200, x, y, _autoHeight: note._autoHeight !== false,
                         title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
                     });
                     textDoc.layoutKey = "layout_" + note.title;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2e7fd1b21..c9c369fba 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -271,8 +271,7 @@ export class CurrentUserUtils {
         }
 
         if (doc["template-notes"] === undefined) {
-            doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc,
-            doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc],
+            doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc],
                 { title: "Note Layouts", _height: 75, system: true }));
         } else {
             const curNoteTypes = Cast(doc["template-notes"], Doc, null);
@@ -943,7 +942,7 @@ export class CurrentUserUtils {
         doc.system = true;
         doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
         doc.title = Doc.CurrentUserEmail;
-        doc.userColor = "orange";
+        doc.userColor = doc.userColor || "#12121233";
         doc._raiseWhenDragged = true;
         doc.activeInkPen = doc;
         doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
@@ -984,14 +983,14 @@ export class CurrentUserUtils {
         doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
 
         // uncomment this to setup a default note style that uses the custom header layout
-        PromiseValue(doc.emptyHeader).then(factory => {
-            if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) {
-                const deleg = Doc.delegateDragFactory(factory as Doc);
-                deleg.title = "header";
-                doc.defaultTextLayout = new PrefetchProxy(deleg);
-                Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
-            }
-        });
+        // PromiseValue(doc.emptyHeader).then(factory => {
+        //     if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) {
+        //         const deleg = Doc.delegateDragFactory(factory as Doc);
+        //         deleg.title = "header";
+        //         doc.defaultTextLayout = new PrefetchProxy(deleg);
+        //         Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
+        //     }
+        // });
         return doc;
     }
 
@@ -1128,6 +1127,20 @@ export class CurrentUserUtils {
         CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
     }
 
+    public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number) {
+        const tbox = Docs.Create.TextDocument("", {
+            _width: width || 200, _height: height || 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
+            _fontFamily: StrCast(Doc.UserDoc().fontFamily), title
+        });
+        const template = FormattedTextBox.DefaultLayout;
+        if (template instanceof Doc) {
+            tbox._width = NumCast(template._width);
+            tbox.layoutKey = "layout_" + StrCast(template.title);
+            Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
+        }
+        return tbox;
+    }
+
     public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); }
     public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
     public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 5b8fe3576..fc8bc382e 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -52,7 +52,7 @@ export class SettingsManager extends React.Component<{}> {
     }
 
     @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice");
-    @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value);
+    @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined);
     @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
     @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
     @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex));
@@ -109,8 +109,8 @@ export class SettingsManager extends React.Component<{}> {
                     {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
                 </select>
                 <div>
-                    <div className="preferences-check">Show title</div>
-                    <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = !Doc.UserDoc().showTitle} checked={BoolCast(Doc.UserDoc().showTitle)} />
+                    <div className="preferences-check">Show header</div>
+                    <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : "creationDate"} checked={Doc.UserDoc().showTitle !== undefined} />
                 </div>
                 <div>
                     <div className="preferences-check">Alt Buttons</div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6fdbac41b..de50deee7 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -51,6 +51,7 @@ const storage = "data";
 interface ValidatedUser {
     user: User;
     notificationDoc: Doc;
+    userColor: string;
 }
 
 
@@ -58,7 +59,7 @@ interface ValidatedUser {
 export class SharingManager extends React.Component<{}> {
     public static Instance: SharingManager;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
-    @observable private users: ValidatedUser[] = []; // the list of users with notificationDocs
+    @observable public users: ValidatedUser[] = []; // the list of users with notificationDocs
     @observable private targetDoc: Doc | undefined; // the document being shared
     @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared
     // @observable private copied = false;
@@ -129,9 +130,10 @@ export class SharingManager extends React.Component<{}> {
                     const userDocument = await DocServer.GetRefField(user.userDocumentId);
                     if (userDocument instanceof Doc) {
                         const notificationDoc = await Cast(userDocument.mySharedDocs, Doc);
+                        const userColor = StrCast(userDocument.userColor);
                         runInAction(() => {
                             if (notificationDoc instanceof Doc) {
-                                this.users.push({ user, notificationDoc });
+                                this.users.push({ user, notificationDoc, userColor });
                             }
                         });
                     }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 7629ed173..979b1198a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -308,7 +308,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     render() {
         if (!this.view0) return (null);
 
-        const isText = this.view0.props.Document[Doc.LayoutFieldKey(this.view0.props.Document)] instanceof RichTextField;
+        const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
         const considerPull = isText && this.considerGoogleDocsPull;
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e4e27bec7..108b896a5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -434,21 +434,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
-            if (e.ctrlKey && !element.props.Document._nativeHeight) element.toggleNativeDimensions();
+        SelectionManager.SelectedDocuments().forEach(action((docView: DocumentView) => {
+            if (e.ctrlKey && !docView.props.Document._nativeHeight) docView.toggleNativeDimensions();
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = Document(element.rootDoc);
-                let nwidth = returnVal(element.NativeWidth?.(), doc._nativeWidth);
-                let nheight = returnVal(element.NativeHeight?.(), doc._nativeHeight);
+                const doc = Document(docView.rootDoc);
+                let nwidth = returnVal(docView.NativeWidth?.(), doc._nativeWidth);
+                let nheight = returnVal(docView.NativeHeight?.(), doc._nativeHeight);
                 const width = (doc._width || 0);
                 let height = (doc._height || (nheight / nwidth * width));
                 height = !height || isNaN(height) ? 20 : height;
-                const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
+                const scale = docView.props.ScreenToLocalTransform().Scale * docView.props.ContentScaling();
                 if (nwidth && nheight) {
                     if (nwidth / nheight !== width / height) {
                         height = nheight / nwidth * width;
                     }
-                    if (e.ctrlKey || (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+                    if (e.ctrlKey || (!dragBottom || !docView.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
                         if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
                         else dW = dH * nwidth / nheight;
                     }
@@ -458,7 +458,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 const fixedAspect = (nwidth && nheight);
-                const fieldKey = Doc.LayoutFieldKey(doc);
+                const fieldKey = docView.LayoutFieldKey;
                 if (fixedAspect && (!nwidth || !nheight)) {
                     doc[DataSym][fieldKey + "-nativeWidth"] = doc._nativeWidth = nwidth = doc._width || 0;
                     doc[DataSym][fieldKey + "-nativeHeight"] = doc._nativeHeight = nheight = doc._height || 0;
@@ -484,7 +484,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH;
                     }
                     else {
-                        if (!fixedAspect || (dragBottom && (e.ctrlKey || element.layoutDoc._fitWidth))) {
+                        if (!fixedAspect || (dragBottom && (e.ctrlKey || docView.layoutDoc._fitWidth))) {
                             doc[DataSym][fieldKey + "-nativeHeight"] = doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0);
                         }
                         doc._height = actualdH;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index b231b2171..ba9334c40 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -257,7 +257,7 @@ export class KeyManager {
                 let count = 1;
                 const list: Doc[] = [];
                 const targetDataDoc = Doc.GetProto(first.props.Document);
-                const fieldKey = Doc.LayoutFieldKey(first.props.Document);
+                const fieldKey = first.LayoutFieldKey;
                 const docList = DocListCast(targetDataDoc[fieldKey]);
                 docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
                     count++;
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ee5a390a7..f5f115e01 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -15,6 +15,7 @@ import { Utils } from '../../Utils';
 import { Networking } from '../Network';
 import { Upload } from '../../server/SharedMediaTypes';
 import { basename } from 'path';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
 
 @observer
 export class PreviewCursor extends React.Component<{}> {
@@ -82,15 +83,7 @@ export class PreviewCursor extends React.Component<{}> {
                 else {
                     // creates text document
                     FormattedTextBox.PasteOnLoad = e;
-                    undoBatch(() => PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
-                        _width: 500,
-                        limitHeight: 400,
-                        _autoHeight: true,
-                        _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
-                        x: newPoint[0],
-                        y: newPoint[1],
-                        title: "-pasted text-"
-                    })))();
+                    UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500)), "paste");
                 }
             } else
                 //pasting in images
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index cb7ffb0e7..7014966c7 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -141,7 +141,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     addDocument = (value: string, shiftDown?: boolean) => {
         this._createAliasSelected = false;
         const key = StrCast(this.props.parent.props.Document._pivotField);
-        const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value });
+        const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value });
         const onLayoutDoc = this.onLayoutDoc(key);
         (onLayoutDoc ? newDoc : newDoc[DataSym])[key] = this.getValue(this.props.heading);
         const docs = this.props.parent.childDocList;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0f8a5df0d..ff45cf71b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -98,7 +98,7 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this.getElement(!this.SelectedCollection ? [button] :
             [<CollectionViewBaseChrome key="chrome"
                 docView={this.SelectedCollection}
-                fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)}
+                fieldKey={this.SelectedCollection.LayoutFieldKey}
                 type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
                 prop,
                 button]);
@@ -521,7 +521,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     }
     get document() { return this.props.docView.props.Document; }
     @computed get dataField() {
-        return this.document[Doc.LayoutFieldKey(this.document)];
+        return this.document[this.props.docView.LayoutFieldKey];
     }
     @computed get childDocs() {
         return DocListCast(this.dataField);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index fd2ae03d8..44d264b36 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -132,7 +132,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     addDocument = (value: string, shiftDown?: boolean) => {
         if (!value) return false;
         const key = StrCast(this.props.parent.props.Document._pivotField);
-        const newDoc = Docs.Create.TextDocument(value, { _height: 18, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value, _autoHeight: true });
+        const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
         newDoc[key] = this.getValue(this.props.heading);
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
         const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -265,7 +265,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
         ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
             Doc.GetProto(this.props.parent.props.Document)[name] = "";
-            const created = Docs.Create.TextDocument("", { title: name, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 250, _autoHeight: true });
+            const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
             if (created) {
                 if (this.props.parent.Document.isTemplateDoc) {
                     Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index dc1d5a8dd..d9c4d274a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -290,7 +290,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                             this.addDocument(Docs.Create.WebDocument(href, { ...options, _fitWidth: true, title: href }));
                         }
                     } else if (text) {
-                        this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 25 }));
+                        this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: StrCast(Doc.UserDoc().showTitle), _width: 100, _height: 25 }));
                     }
                     return;
                 }
@@ -470,7 +470,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 completed?.();
             } else {
                 if (text && !text.includes("https://")) {
-                    UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
+                    UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
                 }
             }
             disposer();
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 61c8f580d..e8944a5eb 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -533,7 +533,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 ContainingCollectionDoc={this.props.containingCollection}
             />;
         return <>
-            <div className={`docContainer${Doc.IsSystem(this.props.document) ? "-system" : ""}`} ref={this._tref} title="click to edit title"
+            <div className={`docContainer${Doc.IsSystem(this.props.document) ? "-system" : ""}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open"
                 style={{
                     fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 854254195..13aa60e7c 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -376,7 +376,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @undoBatch
     createRow = action(() => {
-        this.props.addDocument(Docs.Create.TextDocument("", { title: "", _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 30 }));
+        this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
         this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
     });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 85641531d..24c76e88e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -949,7 +949,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate,
             LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString,
             FreezeDimensions: this.props.freezeChildDimensions,
-            layoutKey: undefined,
+            layoutKey: StrCast(this.props.Document.childLayoutKey),
             setupDragLines: this.setupDragLines,
             dontRegisterView: this.props.dontRegisterView,
             rootSelected: childData ? this.rootSelected : returnFalse,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6436e74e9..4f2399962 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -24,6 +24,7 @@ import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
 import { Id } from "../../../../fields/FieldSymbols";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -138,20 +139,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 e.stopPropagation();
             } else if (!e.ctrlKey && !e.metaKey) {
                 FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.ChildLayoutString ? e.key : "";
-                const tbox = Docs.Create.TextDocument("", {
-                    _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
-                    _fontFamily: StrCast(Doc.UserDoc().fontFamily),
-                    _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
-                    title: "-typed text-"
-                });
-                const template = FormattedTextBox.DefaultLayout;
-                if (template instanceof Doc) {
-                    tbox._width = NumCast(template._width);
-                    tbox.layoutKey = "layout_" + StrCast(template.title);
-                    Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
-                }
                 FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
-                this.props.addLiveTextDocument(tbox);
+                this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100));
                 e.stopPropagation();
             }
     }
@@ -540,7 +529,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             d.page = -1;
             return d;
         });
-        const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+        const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
         const portal = Doc.MakeAlias(summary);
         Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
         Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index ba9c02b5c..d31427829 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -302,7 +302,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
                 (e: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         undoBatch(action(() => {
-                            const text = Docs.Create.TextDocument("", { _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 150, _height: 50 });
+                            const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
                             FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
                             Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
                             this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 88f6a65be..cba65f663 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -26,6 +26,7 @@ import { Scripting } from "../../util/Scripting";
 import Waveform from "react-audio-waveform";
 import axios from "axios";
 import { SnappingManager } from "../../util/SnappingManager";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 
 declare class MediaRecorder {
     // whatever MediaRecorder has
@@ -292,11 +293,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
 
     // creates a text document for dictation
     onFile = (e: any) => {
-        const newDoc = Docs.Create.TextDocument("", {
-            _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: "", _chromeStatus: "disabled",
-            x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
-            _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height)
-        });
+        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).audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 430a1aa45..c31172e22 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -110,7 +110,7 @@
             transform-origin: top left;
             top: 0;
             width: 100%;
-            height: 25;
+            height: 14;
             background: rgba(0, 0, 0, .4);
             text-align: center;
             text-overflow: ellipsis;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5ca57a193..0b4f45dd6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -118,10 +118,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
+    public get LayoutFieldKey() { return StrCast(this.props.layoutKey, Doc.LayoutFieldKey(this.layoutDoc)); }
+    @computed get ShowTitle() {
+        return StrCast(this.layoutDoc._showTitle,
+            !Doc.IsSystem(this.layoutDoc) && this.rootDoc.type === DocumentType.RTF && !this.props.treeViewDoc ?
+                (this.dataDoc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") :
+                undefined);
+    }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeWidth"], (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0))); }
-    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? Doc.LayoutFieldKey(this.layoutDoc) + "-" : "_") + "nativeHeight"], (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0))); }
+    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? this.LayoutFieldKey + "-" : "_") + "nativeWidth"], (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0))); }
+    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? this.LayoutFieldKey + "-" : "_") + "nativeHeight"], (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0))); }
     @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); }
@@ -737,8 +744,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.props.bringToFront(this.props.Document, true);
             const wid = this.Document[WidthSym]();    // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it.
             const hgt = this.Document[HeightSym]();
-            this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = wid;
-            this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = hgt;
+            this.props.Document[DataSym][this.LayoutFieldKey + "-nativeWidth"] = wid;
+            this.props.Document[DataSym][this.LayoutFieldKey + "-nativeHeight"] = hgt;
         }
     }
 
@@ -887,8 +894,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     chromeHeight = () => {
-        const showTitle = StrCast(this.layoutDoc._showTitle);
-        const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
+        const excluded = ["PresBox", "FormattedTextBox", "FontIconBox"];
+        const showTextTitle = this.ShowTitle && !excluded.includes(StrCast(this.layoutDoc.layout)) ? this.ShowTitle : undefined;
         return showTextTitle ? 25 : 1;
     }
 
@@ -1014,10 +1021,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>;
         }
 
-        const showTitle = StrCast(this.layoutDoc._showTitle);
         const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
         const showCaption = StrCast(this.layoutDoc._showCaption);
-        const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
+        const showTextTitle = this.ShowTitle && this.rootDoc.type === DocumentType.RTF ? this.ShowTitle : undefined;
         const captionView = (!showCaption ? (null) :
             <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
@@ -1033,19 +1039,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     onClick={this.onClickFunc}
                     layoutKey={this.finalLayoutKey} />
             </div>);
-        const titleView = (!showTitle ? (null) :
+        const titleView = (!this.ShowTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
                 position: showTextTitle ? "relative" : "absolute",
+                background: SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.UserDoc().userColor) : "rgba(0,0,0,0.4)"),
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
-                    contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
-                    display={"block"} height={72} fontSize={12}
-                    GetValue={() => (this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
-                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)}
+                    contents={this.ShowTitle.split(";").map(field => field + ":" + (this.props.DataDoc || this.props.Document)[field]?.toString()).join(" ")}
+                    display={"block"} fontSize={10}
+                    GetValue={() => ""}
+                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[this.ShowTitle] = value) ? true : true)}
                 />
             </div>);
-        return !showTitle && !showCaption ?
+        return !this.ShowTitle && !showCaption ?
             this.contents :
             <div className="documentView-styleWrapper" >
                 {this.Document.type !== DocumentType.RTF ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 41622dbc9..dac6d3bf8 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -32,6 +32,7 @@ import "./WebBox.scss";
 import "../pdf/PDFViewer.scss";
 import React = require("react");
 import { Tooltip } from '@material-ui/core';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 const htmlToText = require("html-to-text");
 
 type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -498,15 +499,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         e.preventDefault();
         e.stopPropagation();
 
-        const clipDoc = Doc.MakeAlias(this.dataDoc);
-        clipDoc._fitWidth = true;
-        clipDoc._width = this.marqueeWidth();
-        clipDoc._height = this.marqueeHeight();
-        clipDoc._scrollTop = this.marqueeY();
-        const targetDoc = Docs.Create.TextDocument("", { _width: 125, _height: 125, title: "Note linked to " + this.props.Document.title });
-        Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
-        clipDoc.rootDocument = targetDoc;
-        targetDoc.layoutKey = "layout";
+        const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 125, 125);
         const annotationDoc = this.highlight("rgba(173, 216, 230, 0.35)"); // hyperlink color
         if (annotationDoc) {
             DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 0332bb4fd..a4114ed2c 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,5 +1,5 @@
-import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
-import { Doc, DocListCast, Field, LayoutSym } from "../../../../fields/Doc";
+import { IReactionDisposer, observable, computed, action } from "mobx";
+import { Doc, DocListCast, Field } from "../../../../fields/Doc";
 import { List } from "../../../../fields/List";
 import { listSpec } from "../../../../fields/Schema";
 import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c0c3bdd80..1b7346e4d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -15,7 +15,7 @@ import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, Wid
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
-import { Id } from '../../../../fields/FieldSymbols';
+import { Id, Copy } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from "../../../../fields/RichTextField";
@@ -577,6 +577,7 @@ 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" });
         const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
         DocListCast(noteTypesDoc?.data).forEach(note => {
             changeItems.push({
@@ -586,7 +587,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }), icon: "eye"
             });
         });
-        changeItems.push({ description: "plain", event: undoBatch(() => Doc.setNativeView(this.rootDoc)), icon: "eye" });
         !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
@@ -618,7 +618,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
         // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        !Doc.UserDoc().noviceMode && appearanceItems.push({ description: "Reset default note style", event: () => this.rootDoc.layoutKey = "layout", icon: "eye" });
         !Doc.UserDoc().noviceMode && appearanceItems.push({
             description: "Make Default Layout", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
@@ -639,6 +638,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.rootDoc._width = this.layoutDoc._width || 300;  // are stored on the template, since we're getting rid of the old template
                         this.rootDoc._height = this.layoutDoc._height || 200;  // we need to copy them over to the root.  This should probably apply to all '_' fields
                         this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
+                        this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null);
+                        this.rootDoc.header = Cast(this.layoutDoc.header, RichTextField, null)?.[Copy]();
                     }, 10);
                 }
                 Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 45a862954..6973b073c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -34,6 +34,7 @@ const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import React = require("react");
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
 import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 const _global = (window /* browser */ || global /* node */) as any;
@@ -621,11 +622,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         clipDoc._width = this.marqueeWidth();
         clipDoc._height = this.marqueeHeight();
         clipDoc._scrollTop = this.marqueeY();
-        const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
+        const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 100, 100);
         Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
         clipDoc.rootDocument = targetDoc;
-        DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
-        targetDoc.layoutKey = "layout";
+        // DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
+        // targetDoc.layoutKey = "layout";
         // const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         // Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
         // const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() });
-- 
cgit v1.2.3-70-g09d2


From 5dc5009da8018d79620a42b75833d5b4335e0f56 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Sep 2020 03:01:39 -0400
Subject: from last

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1b7346e4d..188ed03da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -639,7 +639,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.rootDoc._height = this.layoutDoc._height || 200;  // we need to copy them over to the root.  This should probably apply to all '_' fields
                         this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
                         this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null);
-                        this.rootDoc.header = Cast(this.layoutDoc.header, RichTextField, null)?.[Copy]();
                     }, 10);
                 }
                 Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
-- 
cgit v1.2.3-70-g09d2


From 80bbbb3842cb89949d2b949ce8f5a2ed3921b112 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Sep 2020 10:46:27 -0400
Subject: fixed deleting text links to work when there are multiple links in
 the same linkAnchor mark.  addressed problem with text getting a negative
 height when its resized to fit content in a preview window when its also
 displayed in freeform.

---
 src/client/util/SharingManager.tsx                 |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 16 ++----
 .../formattedText/FormattedTextBoxComment.tsx      | 59 ++++++++++++++--------
 src/client/views/nodes/formattedText/marks_rts.ts  | 23 +++++----
 .../nodes/formattedText/prosemirrorPatches.js      |  6 ++-
 5 files changed, 59 insertions(+), 49 deletions(-)

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

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index de50deee7..87c620d6d 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -445,7 +445,7 @@ export class SharingManager extends React.Component<{}> {
         const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
 
         // the list of users shared with
-        const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc }) => {
+        const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc, userColor }) => {
             const userKey = `ACL-${user.email.replace('.', '_')}`;
             const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
             const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
@@ -461,7 +461,7 @@ export class SharingManager extends React.Component<{}> {
                             <select
                                 className={"permissions-dropdown"}
                                 value={permissions}
-                                onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
+                                onChange={e => this.setInternalSharing({ user, notificationDoc, userColor }, e.currentTarget.value)}
                             >
                                 {this.sharingOptions(uniform)}
                             </select>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 188ed03da..83012bab5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1233,7 +1233,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             FormattedTextBoxComment.textBox = this;
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
-            FormattedTextBoxComment.update(editor, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
+            const target = (e.target as any).parentElement; // hrefs are store don the database of the <a> node that wraps the hyerlink <span>
+            FormattedTextBoxComment.update(editor, undefined, target?.dataset?.targethrefs);
         }
         (e.nativeEvent as any).formattedHandled = true;
 
@@ -1487,7 +1488,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @action
     tryUpdateHeight(limitHeight?: number) {
         let scrollHeight = this.ProseRef?.scrollHeight || 0;
-        if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+        if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1);
             if (limitHeight && scrollHeight > limitHeight) {
                 scrollHeight = limitHeight;
@@ -1506,18 +1507,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }, 10);
             } else {
                 try {
-                    // let ele = this._boxRef.current;
-                    // while (ele && ele.className !== "documentView-contentsView") ele = ele.parentElement as any;
-                    // if (ele) {
-                    //     const docHeight = Number(getComputedStyle(ele).height.replace("px", ""));
-                    //     const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
-                    //     const outer = docHeight - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
-                    //     this.rootDoc._height = newHeight + outer;
-                    //     this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
-                    // }
                     const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
                     const outer = this.rootDoc[HeightSym]() - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
-                    this.rootDoc._height = newHeight + outer;
+                    this.rootDoc._height = newHeight + Math.max(0, outer);
                     this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
                 } catch (e) { console.log("Error in tryUpdateHeight"); }
             }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 1bf885636..249aca812 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
-import { action } from "mobx";
+import { action, observable } from "mobx";
 import { Mark, ResolvedPos } from "prosemirror-model";
 import { EditorState, Plugin } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
@@ -23,6 +23,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
 import './FormattedTextBoxComment.scss';
 import { schema } from "./schema_rts";
 import React = require("react");
+import { observer } from "mobx-react";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -60,6 +61,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
 // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
 // this will also display metadata information about text when the view is configured to display things like other people who authored text.
 // 
+
 export class FormattedTextBoxComment {
     static tooltip: HTMLElement;
     static tooltipText: HTMLElement;
@@ -72,6 +74,13 @@ export class FormattedTextBoxComment {
 
     static _deleteRef: Opt<HTMLDivElement | null>;
     static _followRef: Opt<HTMLDivElement | null>;
+    static _nextRef: Opt<HTMLDivElement | null>;
+
+    static _lastState?: EditorState;
+    static _lastView?: EditorView;
+
+    @observable static _hrefInd = 0;
+    static _hrefs: string[] | undefined = [];
 
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
@@ -101,6 +110,8 @@ export class FormattedTextBoxComment {
                     if (linkDoc.author) {
                         if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) {
                             this.deleteLink();
+                        } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) {
+                            FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length])
                         } else {
                             FormattedTextBoxComment.linkDoc = undefined;
                             if (linkDoc.type !== DocumentType.LINK) {
@@ -128,7 +139,6 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
         LinkDocPreview.LinkInfo = undefined;
         DocumentLinksButton.EditLink = undefined;
-        //FormattedTextBoxComment.tooltipText = undefined;
         FormattedTextBoxComment.Hide();
     });
 
@@ -164,14 +174,20 @@ export class FormattedTextBoxComment {
     }
 
     static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
-        const state = view.state;
         // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) {
+        if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) {
             return;
         }
+        FormattedTextBoxComment._lastState = lastState;
+        FormattedTextBoxComment._lastView = view;
+        FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined;
+        FormattedTextBoxComment._hrefInd = 0;
         FormattedTextBoxComment.linkDoc = undefined;
+        FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]);
+    }
 
+    static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
+        const state = view.state;
         const textBox = FormattedTextBoxComment.textBox;
         if (!textBox || !textBox.props) {
             return;
@@ -209,7 +225,7 @@ export class FormattedTextBoxComment {
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             child = child || (nbef && state.selection.$from.nodeBefore);
             const mark = child ? findLinkMark(child.marks) : undefined;
-            const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl;
+            const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href;
             if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
                 try {
                     ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
@@ -245,34 +261,35 @@ export class FormattedTextBoxComment {
                             if (target?.author) {
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
 
-                                const title = StrCast(target.title).length > 16 ?
-                                    StrCast(target.title).substr(0, 16) + "..." : target.title;
-
+                                const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title;
 
                                 const docPreview = <div className="FormattedTextBoxComment">
                                     <div className="FormattedTextBoxComment-info">
                                         <div className="FormattedTextBoxComment-title">
                                             {title}
-                                            {FormattedTextBoxComment.linkDoc.description !== "" ? <p className="FormattedTextBoxComment-description">
-                                                {StrCast(FormattedTextBoxComment.linkDoc.description)}</p> : null}
+                                            {FormattedTextBoxComment.linkDoc.description === "" ? (null) :
+                                                <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>}
                                         </div>
                                         <div className="wrapper" style={{ float: "right" }}>
+                                            {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top">
+                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}>
+                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" />
+                                                </div>
+                                            </Tooltip>}
 
                                             <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
-                                                <div className="FormattedTextBoxComment-button"
-                                                    ref={(r) => this._deleteRef = r}>
-                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
-                                                        size="sm" /></div>
+                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}>
+                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" />
+                                                </div>
                                             </Tooltip>
 
                                             <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
-                                                <div className="FormattedTextBoxComment-button"
-                                                    ref={(r) => this._followRef = r}>
-                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
-                                                        size="sm" />
+                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}>
+                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" />
                                                 </div>
                                             </Tooltip>
-                                        </div> </div>
+                                        </div>
+                                    </div>
                                     <div className="FormattedTextBoxComment-preview-wrapper">
                                         <ContentFittingDocumentView
                                             Document={target}
@@ -304,8 +321,6 @@ export class FormattedTextBoxComment {
                                     </div>
                                 </div>;
 
-
-
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
 
                                 ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 066ceb5c8..cca7ea013 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -36,20 +36,21 @@ export const marks: { [index: string]: MarkSpec } = {
         }],
         toDOM(node: any) {
             const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
+            const targethrefs = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.href, "");
             const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
             return node.attrs.docref && node.attrs.title ?
                 ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
-                node.attrs.allLinks.length === 1 ?
-                    ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0] :
-                    ["div", { class: "prosemirror-anchor" },
-                        ["span", { class: "prosemirror-linkBtn" },
-                            ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
-                            ["input", { class: "prosemirror-hrefoptions" }],
-                        ],
-                        ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
-                            ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
-                        )]
-                    ];
+                //node.attrs.allLinks.length === 1 ?
+                ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, "data-targethrefs": targethrefs, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0];
+            // ["div", { class: "prosemirror-anchor" },
+            //     ["span", { class: "prosemirror-linkBtn" },
+            //         ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
+            //         ["input", { class: "prosemirror-hrefoptions" }],
+            //     ],
+            //     ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
+            //         ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
+            //     )]
+            // ];
         }
     },
 
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 0969ea4ef..746c93868 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -146,7 +146,9 @@ function isInSetWithAttrs(mark, set, attrs) {
     for (var i = 0; i < set.length; i++) {
         if (set[i].type == mark) {
             if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
-                return p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey]);
+                if (p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey])) return true;
+                set[i].attrs.allLinks = Array.from(set[i].attrs.allLinks).filter(a => !Array.from(attrs.allLinks.map(al => al.targetId)).includes(a.targetId) || !Array.from(attrs.allLinks.map(al => al.linkId).includes(a.linkId)))
+                return false;
             }, true)) {
                 return set[i];
             }
@@ -178,7 +180,7 @@ function removeMarkWithAttrs(tr, from, to, mark, attrs) {
                 var style = toRemove[i], found$1 = (void 0);
                 for (var j = 0; j < matched.length; j++) {
                     var m = matched[j];
-                    if (m.step == step - 1 && style.eq(matched[j].style)) { found$1 = m; }
+                    if (m.step == step - 1 /*&& style.eq(matched[j].style)*/) { found$1 = m; }  // bcz: not sure what this is even trying to do, but style.eq doesn't seem to exist anymore
                 }
                 if (found$1) {
                     found$1.to = end;
-- 
cgit v1.2.3-70-g09d2


From c260df7a30a5601d784fe9ccaccd805fbefde51d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 18 Sep 2020 10:27:18 -0400
Subject: changed ACL to acl.   fixed title to read values from data doc
 preferentially over layout doc.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/SharingManager.tsx                 | 30 +++++++++++-----------
 src/client/views/PropertiesView.tsx                |  6 ++---
 src/client/views/collections/CollectionMenu.tsx    |  4 +--
 .../views/collections/CollectionSchemaHeaders.tsx  |  4 +--
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/FilterBox.tsx               |  2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    | 14 +++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  6 ++---
 src/client/views/search/SearchBox.tsx              |  6 ++---
 src/fields/Doc.ts                                  | 10 ++++----
 src/fields/Schema.ts                               |  2 +-
 src/fields/util.ts                                 | 14 +++++-----
 13 files changed, 52 insertions(+), 52 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ecb1342f4..f9e3add84 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -598,7 +598,7 @@ export namespace Docs {
             viewDoc.author = Doc.CurrentUserEmail;
             viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
 
-            if (Doc.UserDoc()?.defaultAclPrivate) viewDoc["ACL-Public"] = dataDoc["ACL-Public"] = "Not Shared";
+            if (Doc.UserDoc()?.defaultAclPrivate) viewDoc["acl-Public"] = dataDoc["acl-Public"] = "Not Shared";
 
             return Doc.assign(viewDoc, delegateProps, true);
         }
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 87c620d6d..7991022d2 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -152,13 +152,13 @@ export class SharingManager extends React.Component<{}> {
 
         const target = targetDoc || this.targetDoc!;
         const key = StrCast(group.groupName).replace(".", "_");
-        const ACL = `ACL-${key}`;
+        const acl = `acl-${key}`;
 
         const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
 
         docs.forEach(doc => {
-            doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
-            GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc);
+            doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
+            GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc);
 
             if (key !== "Public") {
                 const members: string[] = JSON.parse(StrCast(group.members));
@@ -198,7 +198,7 @@ export class SharingManager extends React.Component<{}> {
         }
         else {
             docs.forEach(doc => {
-                if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("ACL-Public", permission, doc);
+                if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("acl-Public", permission, doc);
             });
         }
     }
@@ -225,9 +225,9 @@ export class SharingManager extends React.Component<{}> {
     removeGroup = (group: Doc) => {
         if (group.docsShared) {
             DocListCast(group.docsShared).forEach(doc => {
-                const ACL = `ACL-${StrCast(group.groupName)}`;
+                const acl = `acl-${StrCast(group.groupName)}`;
 
-                distributeAcls(ACL, SharingPermissions.None, doc);
+                distributeAcls(acl, SharingPermissions.None, doc);
 
                 const members: string[] = JSON.parse(StrCast(group.members));
                 const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -244,14 +244,14 @@ export class SharingManager extends React.Component<{}> {
         const { user, notificationDoc } = recipient;
         const target = targetDoc || this.targetDoc!;
         const key = user.email.replace('.', '_');
-        const ACL = `ACL-${key}`;
+        const acl = `acl-${key}`;
 
 
         const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
 
         docs.forEach(doc => {
-            doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
-            GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc);
+            doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
+            GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc);
 
             if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc);
             else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc));
@@ -445,8 +445,8 @@ export class SharingManager extends React.Component<{}> {
         const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
 
         // the list of users shared with
-        const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc, userColor }) => {
-            const userKey = `ACL-${user.email.replace('.', '_')}`;
+        const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc, userColor }) => {
+            const userKey = `acl-${user.email.replace('.', '_')}`;
             const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
             const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
 
@@ -503,7 +503,7 @@ export class SharingManager extends React.Component<{}> {
                         <span className={"padding"}>Me</span>
                         <div className="edit-actions">
                             <div className={"permissions-dropdown"}>
-                                {targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]}
+                                {targetDoc?.[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`]}
                             </div>
                         </div>
                     </div>
@@ -514,12 +514,12 @@ export class SharingManager extends React.Component<{}> {
         const publicDoc = new Doc;
         publicDoc.groupName = "Public";
         // the list of groups shared with
-        const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`ACL-${StrCast(groupName).replace('.', '_')}`) : true);
+        const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true);
         groupListMap.unshift(publicDoc);
         const groupListContents = groupListMap.map(group => {
-            const groupKey = `ACL-${StrCast(group.groupName)}`;
+            const groupKey = `acl-${StrCast(group.groupName)}`;
             const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
-            const permissions = uniform ? StrCast(targetDoc?.[`ACL-${StrCast(group.groupName)}`]) : "-multiple-";
+            const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.groupName)}`]) : "-multiple-";
 
             return !permissions ? (null) : (
                 <div
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index b12d539e4..0dd749b07 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -168,7 +168,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             const rows: JSX.Element[] = [];
             const noviceReqFields = ["author", "creationDate", "tags"];
             const noviceLayoutFields = ["_curPage"];
-            const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL"))),
+            const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("acl"))),
             ...noviceReqFields, ...noviceLayoutFields];
             for (const key of noviceKeys.sort()) {
                 const docvals = new Set<any>();
@@ -416,13 +416,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
         //     if (Doc.UserDoc().sidebarUsersDisplayed) {
         //         for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) {
-        //             if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
+        //             if (value === true && !this.selectedDoc![`acl-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
         //         }
         //     }
         // })
 
         // shifts the current user, owner, public to the top of the doc.
-        tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.every(doc => doc["ACL-Public"] === docs[0]["ACL-Public"]) ? (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None) : "-multiple-"));
+        tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-"));
         tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-"));
         if (Doc.CurrentUserEmail !== target.author && docs.every(doc => doc.author === docs[0].author)) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner"));
 
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 087942f11..4c5a71ace 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -893,14 +893,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
             if (docs instanceof Doc) {
                 const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
                     key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
-                    (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_"));
+                    (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "acl" && key[0] !== "_"));
                 return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
             } else {
                 const keys = new Set<string>();
                 docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
                 const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
                     key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
-                    (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_"));
+                    (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "acl" && key[0] !== "_"));
                 return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
             }
         }
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index d2a1234ed..c02e88829 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -299,7 +299,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
                 this.updateFilter();
                 let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
                 const blockedkeys = ["system", "title-custom", "limitHeight", "proto", "x", "y", "zIndex", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
-                keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL"));
+                keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("acl"));
                 if (keyOptions.length) {
                     this.onSelect(keyOptions[0]);
                 } else if (this._searchTerm !== "" && this.props.canAddNew) {
@@ -333,7 +333,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
             this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
 
         const blockedkeys = ["proto", "x", "y", "zIndex", "_timeStampOnEnter", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
-        keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL"));
+        keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("acl"));
 
         const options = keyOptions.map(key => {
             return <div key={key} className="key-option" style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7ff8b635c..68328ea4b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1045,10 +1045,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
-                    contents={this.ShowTitle.split(";").map(field => field + ":" + (this.props.DataDoc || this.props.Document)[field]?.toString()).join(" ")}
+                    contents={this.ShowTitle.split(";").map(field => field + ":" + (this.dataDoc || this.props.Document)[field]?.toString()).join(" ")}
                     display={"block"} fontSize={10}
                     GetValue={() => ""}
-                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[this.ShowTitle] = value) ? true : true)}
+                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc || this.props.Document)[this.ShowTitle] = value) ? true : true)}
                 />
             </div>);
         return !this.ShowTitle && !showCaption ?
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 7a010532f..9fd81bd90 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -47,7 +47,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
 
         const keys = new Set<string>(noviceFields);
         this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
-        return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort();
+        return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("acl")) || noviceFields.includes(key)).sort();
     }
     /**
      * Responds to clicking the check box in the flyout menu
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index a4114ed2c..b39a845db 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,5 +1,5 @@
 import { IReactionDisposer, observable, computed, action } from "mobx";
-import { Doc, DocListCast, Field } from "../../../../fields/Doc";
+import { Doc, DocListCast, Field, DataSym } from "../../../../fields/Doc";
 import { List } from "../../../../fields/List";
 import { listSpec } from "../../../../fields/Schema";
 import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
@@ -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[this._fieldKey] || (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
+            const dashVal = this._dashDoc[DataSym][this._fieldKey] ?? this._dashDoc[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) || "";
@@ -94,7 +94,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                     type="checkbox" checked={boolVal}
                     onChange={e => {
                         if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
-                        this._dashDoc![this._fieldKey] = e.target.checked;
+                        Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
                     }}
                 />;
             }
@@ -155,22 +155,22 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 if (modText) {
                     //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
                     DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
-                    this._dashDoc![this._fieldKey] = modText;
+                    Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
                 } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
                 else if (nodeText.startsWith(":=")) {
-                    this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
+                    this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
                 } else if (nodeText.startsWith("=:=")) {
                     Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
                 } else {
                     if (Number(newText).toString() === newText) {
                         if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
-                        this._dashDoc![this._fieldKey] = Number(newText);
+                        Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
                     } else {
                         const splits = newText.split(this.multiValueDelimeter);
                         if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
                             const strVal = splits.length > 1 ? new List<string>(splits) : newText;
                             if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
-                            this._dashDoc![this._fieldKey] = strVal;
+                            Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
                         }
                     }
                 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 83012bab5..dc0fafd24 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -794,9 +794,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     tr = tr.addMark(pos, pos + node.nodeSize, link);
                 }
             });
-            OVERRIDE_ACL(true);
+            OVERRIDE_acl(true);
             this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
-            OVERRIDE_ACL(false);
+            OVERRIDE_acl(false);
         }
     }
     componentDidMount() {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b37ae02c3..3238e4dc6 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -530,10 +530,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                                         style={{ cursor: "hand", color: "black", padding: 1, position: "relative" }} /></div>
                                 </Tooltip>
                             </div>
-                            <div style={{ position: "absolute", left: 200, width: 30, zIndex: 9000, color: "grey", background: "white", }}>
+                            <div style={{ position: "absolute", left: 220, width: 30, zIndex: 9000, color: "grey", background: "white", }}>
                                 {`${this._results.length}` + " of " + `${this.realTotalResults}`}
                             </div>
-                            <div style={{ cursor: "default", left: 235, position: "absolute", }}>
+                            {/* <div style={{ cursor: "default", left: 235, position: "absolute", }}>
                                 <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} >
                                     <div>
                                         <FontAwesomeIcon icon={"filter"} size="lg"
@@ -542,7 +542,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                                             onClick={action(() => this.setSearchFilter(this.currentSelectedCollection, this.filter ? undefined : this.docsforfilter))} />
                                     </div>
                                 </Tooltip>
-                            </div>
+                            </div> */}
                             {this.scopeButtons}
                         </div>
                     </div >
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a8950f1ee..ba7c9c7da 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -121,7 +121,7 @@ const AclMap = new Map<string, symbol>([
 export function fetchProto(doc: Doc) {
     const permissions: { [key: string]: symbol } = {};
 
-    Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+    Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
 
     if (Object.keys(permissions).length) doc[AclSym] = permissions;
 
@@ -253,7 +253,7 @@ export class Doc extends RefField {
                     const prev = GetEffectiveAcl(this);
                     this[UpdatingFromServer] = true;
                     this[fKey] = value;
-                    if (fKey.startsWith("ACL")) {
+                    if (fKey.startsWith("acl")) {
                         fetchProto(this);
                     }
                     this[UpdatingFromServer] = false;
@@ -261,7 +261,7 @@ export class Doc extends RefField {
                         DocServer.GetRefField(this[Id], true);
                     }
                 };
-                if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+                if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
                     await fn();
                 } else {
@@ -775,7 +775,7 @@ export namespace Doc {
             }
         });
         copy.author = Doc.CurrentUserEmail;
-        Doc.UserDoc().defaultAclPrivate && (copy["ACL-Public"] = "Not Shared");
+        Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
         return copy;
     }
 
@@ -803,7 +803,7 @@ export namespace Doc {
             const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
             target.layoutKey = targetKey;
             applied && (Doc.GetProto(applied).type = templateDoc.type);
-            Doc.UserDoc().defaultAclPrivate && (applied["ACL-Public"] = "Not Shared");
+            Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
             return applied;
         }
         return undefined;
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 23ac50f74..4607e0fd5 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -52,7 +52,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
             return field;
         },
         set(target: any, prop, value, receiver) {
-            receiver.doc && (receiver.doc[prop] = value);  // receiver.doc may be undefined as the result of a change in ACLs
+            receiver.doc && (receiver.doc[prop] = value);  // receiver.doc may be undefined as the result of a change in acls
             return true;
         }
     });
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 82525f92b..00e1683bd 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -111,7 +111,7 @@ export function makeEditable() {
     _setter = _setterImpl;
 }
 var _overrideAcl = false;
-export function OVERRIDE_ACL(val: boolean) {
+export function OVERRIDE_acl(val: boolean) {
     _overrideAcl = val;
 }
 
@@ -168,7 +168,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number,
 
         if (currentUserGroups.includes("Admin")) return AclAdmin;
 
-        // if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified)
+        // if the acl is being overriden or the property being modified is one of the playground fields (which can be freely modified)
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
         let effectiveAcl = AclPrivate;
@@ -198,11 +198,11 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number,
 }
 /**
  * Recursively distributes the access right for a user across the children of a document and its annotations.
- * @param key the key storing the access right (e.g. ACL-groupname)
+ * @param key the key storing the access right (e.g. acl-groupname)
  * @param acl the access right being stored (e.g. "Can Edit")
  * @param target the document on which this access right is being set
- * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the ACLs from the collection)
- * inheritingFromCollection is not currently being used but could be used if ACL assignment defaults change
+ * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
+ * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
  */
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
 
@@ -271,8 +271,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
     if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
 
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
-    if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
-    // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
+    if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
+    // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
-- 
cgit v1.2.3-70-g09d2


From feebbe1a82e3dcff22502f635b67df55c90502b3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 18 Sep 2020 16:53:30 -0400
Subject: added maximum height to stacking items in sidebar using 
 childLimitHeight.  made stacking items non-interactive using
 contentsPointerEvents.  made all links pubilc for now.

---
 src/client/documents/Documents.ts                        |  6 +++++-
 src/client/util/CurrentUserUtils.ts                      |  4 ++--
 src/client/views/DocumentDecorations.tsx                 |  8 ++++----
 src/client/views/collections/CollectionStackingView.tsx  |  6 ++++--
 src/client/views/nodes/ContentFittingDocumentView.tsx    |  8 ++------
 src/client/views/nodes/DocumentView.tsx                  | 16 ++++++++--------
 .../views/nodes/formattedText/FormattedTextBox.tsx       |  2 +-
 src/fields/util.ts                                       |  8 ++------
 8 files changed, 28 insertions(+), 30 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f9e3add84..f7ab955f3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
 import { runInAction } from "mobx";
 import { basename, extname } from "path";
 import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclAddonly } from "../../fields/Doc";
 import { HtmlField } from "../../fields/HtmlField";
 import { InkField } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -51,6 +51,7 @@ import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { FilterBox } from "../views/nodes/FilterBox";
+import { SharingPermissions } from "../../fields/util";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -110,6 +111,8 @@ export interface DocumentOptions {
     _viewScale?: number;
     forceActive?: boolean;
     layout?: string | Doc; // default layout string for a document
+    contentPointerEvents?: string;  // pointer events allowed for content of a document view.  eg. set to "none" sidebar views that are intended to document "menus"
+    childLimitHeight?: number; // whether to limit the height of colleciton children.  0 - means  height can be no bigger than width
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
     childLayoutString?: string; // template string for collection to use to render its children
     hideLinkButton?: boolean; // whether the blue link counter button should be hidden
@@ -980,6 +983,7 @@ export namespace DocUtils {
         Doc.GetProto(linkDoc)["anchor2-useLinkSmallAnchor"] = target.doc.useLinkSmallAnchor;
         linkDoc.linkDisplay = true;
         linkDoc.hidden = true;
+        Doc.GetProto(linkDoc)["acl-Public"] = linkDoc["acl-Public"] = SharingPermissions.Add;
         linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
         Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2?.title');
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c9c369fba..1e054e5a4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -871,7 +871,7 @@ export class CurrentUserUtils {
     // Sharing sidebar is where shared documents are contained
     static setupSharingSidebar(doc: Doc) {
         if (doc.mySharedDocs === undefined) {
-            doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true }));
+            doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true }));
         }
     }
 
@@ -879,7 +879,7 @@ export class CurrentUserUtils {
     static setupImportSidebar(doc: Doc) {
         if (doc.myImportDocs === undefined) {
             doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
-                title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true,
+                title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
                 childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, lockedPosition: true, _chromeStatus: "disabled", system: true
             }));
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 108b896a5..257ddc302 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -171,19 +171,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                if (e.ctrlKey) {
+                if (e.ctrlKey) {    // open an alias in a new tab with Ctrl Key
                     const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                     alias.context = undefined;
                     //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
                     CollectionDockingView.AddSplit(alias, "right");
-                } else if (e.shiftKey) {
+                } else if (e.shiftKey) {   // open centered in a new workspace with Shift Key
                     const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                     alias.context = undefined;
                     alias.x = -alias[WidthSym]() / 2;
                     alias.y = -alias[HeightSym]() / 2;
                     //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
                     CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
-                } else {
+                } else {    // open same document in new tab
                     CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
                 }
             }
@@ -589,7 +589,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
                 </div></Tooltip>);
 
-        const openIcon = !canOpen ? (null) : <Tooltip title={<div className="dash-tooltip">Open In a New Pane</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
+        const openIcon = !canOpen ? (null) : <Tooltip title={<div className="dash-tooltip">Open in Tab (ctrl: as alias, shift: in new collection)</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
             {SelectionManager.SelectedDocuments().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
         </div>
         </Tooltip>;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9c9dad2c9..8b2a30b12 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -215,6 +215,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
+            contentsPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
             parentActive={this.props.active}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.addDocTab}
@@ -239,15 +240,16 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         const nw = NumCast(layoutDoc._nativeWidth) || NumCast(dataDoc?.[`${layoutField}-nativeWidth`]);
         const nh = NumCast(layoutDoc._nativeHeight) || NumCast(dataDoc?.[`${layoutField}-nativeHeight`]);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
+        const hllimit = NumCast(this.layoutDoc.childLimitHeight, -1);
         if (!layoutDoc._fitWidth && nw && nh) {
             const aspect = nw && nh ? nh / nw : 1;
             if (!(this.layoutDoc._columnsFill)) wid = Math.min(this.getDocWidth(d), wid);
-            return wid * aspect;
+            return Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, wid * aspect);
         }
         return layoutDoc._fitWidth ?
             (!nh ? this.props.PanelHeight() - 2 * this.yMargin :
                 Math.min(wid * nh / (nw || 1), this.layoutDoc._autoHeight ? 100000 : this.props.PanelHeight() - 2 * this.yMargin)) :
-            Math.max(20, layoutDoc[HeightSym]());
+            Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, Math.max(20, layoutDoc[HeightSym]()));
     }
 
     columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 75648f9fd..09051da78 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,14 +1,10 @@
 import React = require("react");
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Transform } from "nodemailer/lib/xoauth2";
-import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { ScriptField } from "../../../fields/ScriptField";
+import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnVal, OmitKeys } from "../../../Utils";
-import { dropActionType } from "../../util/DragManager";
-import { CollectionView } from "../collections/CollectionView";
+import { emptyFunction, OmitKeys, returnVal } from "../../../Utils";
 import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 68328ea4b..ff5c4d1e9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,6 +80,7 @@ export interface DocumentViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     pointerEvents?: string;
+    contentsPointerEvents?: string;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
     parentActive: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -892,11 +893,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
     select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
-    chromeHeight = () => {
-        const excluded = ["PresBox", "FormattedTextBox", "FontIconBox"];
-        const showTextTitle = this.ShowTitle && !excluded.includes(StrCast(this.layoutDoc.layout)) ? this.ShowTitle : undefined;
-        return showTextTitle ? 25 : 1;
+    @computed get showOverlappingTitle() {
+        const excluded = ["PresBox", /* "FormattedTextBox", */ "FontIconBox"]; // bcz: shifting the title for texst causes problems with collaborative use when some people see titles, and others don't
+        return this.ShowTitle && !excluded.includes(StrCast(this.layoutDoc.layout)) ? this.ShowTitle : undefined;
     }
+    chromeHeight = () => this.showOverlappingTitle ? 1 : 25;
 
     @computed get finalLayoutKey() {
         if (typeof this.props.layoutKey === "string") {
@@ -913,7 +914,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get contents() {
         const pos = this.props.relative ? "relative " : "absolute";
         TraceMobx();
-        return (<div className="documentView-contentsView" style={{ borderRadius: "inherit", width: "100%", height: "100%" }}>
+        return (<div className="documentView-contentsView" style={{ pointerEvents: this.props.contentsPointerEvents as any, borderRadius: "inherit", width: "100%", height: "100%" }}>
             <DocumentContentsView key={1}
                 docFilters={this.props.docFilters}
                 searchFilterDocs={this.props.searchFilterDocs}
@@ -1022,7 +1023,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
         const showCaption = StrCast(this.layoutDoc._showCaption);
-        const showTextTitle = this.ShowTitle && this.rootDoc.type === DocumentType.RTF ? this.ShowTitle : undefined;
         const captionView = (!showCaption ? (null) :
             <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
@@ -1040,7 +1040,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>);
         const titleView = (!this.ShowTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
-                position: showTextTitle ? "relative" : "absolute",
+                position: this.showOverlappingTitle ? "absolute" : "relative",
                 background: SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.UserDoc().userColor) : "rgba(0,0,0,0.4)"),
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
@@ -1054,7 +1054,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return !this.ShowTitle && !showCaption ?
             this.contents :
             <div className="documentView-styleWrapper" >
-                {this.Document.type !== DocumentType.RTF ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
+                {this.showOverlappingTitle ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
                 {captionView}
             </div>;
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc0fafd24..771b6bbbe 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -949,7 +949,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
         );
 
-        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
+        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight)));
     }
 
     pushToGoogleDoc = async () => {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 159a098cf..9db79ced1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -232,12 +232,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         dataDocChanged = true;
 
         // maps over the aliases of the document
-        const aliases = DocListCast(dataDoc.aliases);
-        if (aliases.length) {
-            aliases.map(alias => {
-                alias !== target && distributeAcls(key, acl, alias, inheritingFromCollection, visited);
-            });
-        }
+        const links = DocListCast(dataDoc.links);
+        links.forEach(alias => distributeAcls(key, acl, alias, inheritingFromCollection, visited));
 
         // maps over the children of the document
         DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
-- 
cgit v1.2.3-70-g09d2


From e4ae6fa6df6bb0118f113bbdf3a40f768405f7b3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 20 Sep 2020 23:53:04 -0400
Subject: fixed undo for bullet points to not take multiple steps and to work
 properly.  no longer adds cursorfiels to the undo stack. fixed sharing
 manager to no create unnecesary documents..

---
 src/client/util/SelectionManager.ts                |   3 -
 src/client/util/SharingManager.tsx                 | 102 +++++----------------
 src/client/views/GlobalKeyHandler.ts               |   2 +
 .../views/collections/CollectionTreeView.tsx       |  15 ++-
 src/client/views/collections/TreeView.tsx          |  10 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 src/fields/Doc.ts                                  |   7 +-
 src/fields/util.ts                                 |  12 ++-
 8 files changed, 62 insertions(+), 97 deletions(-)

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

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 7795a4a59..008ce281c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -37,7 +37,6 @@ export namespace SelectionManager {
                 manager.SelectedDocuments.clear();
                 manager.SelectedDocuments.set(docView, true);
             }
-            Doc.UserDoc().activeSelection = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
         }
         @action
         DeselectDoc(docView: DocumentView): void {
@@ -45,7 +44,6 @@ export namespace SelectionManager {
             if (manager.SelectedDocuments.get(docView)) {
                 manager.SelectedDocuments.delete(docView);
                 docView.props.whenActiveChanged(false);
-                Doc.UserDoc().activeSelection = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
             }
         }
         @action
@@ -54,7 +52,6 @@ export namespace SelectionManager {
             manager.SelectedSchemaDocument = undefined;
             Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments.clear();
-            Doc.UserDoc().activeSelection = new List<Doc>([]);
         }
     }
 
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 7991022d2..d3500970d 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, computed } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
 import Select from "react-select";
@@ -7,7 +7,7 @@ import * as RequestPromise from "request-promise";
 import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { Cast, StrCast } from "../../fields/Types";
-import { distributeAcls, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
+import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from "../../fields/util";
 import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { CollectionView } from "../views/collections/CollectionView";
@@ -148,7 +148,7 @@ export class SharingManager extends React.Component<{}> {
      * @param group 
      * @param permission 
      */
-    setInternalGroupSharing = (group: Doc, permission: string, targetDoc?: Doc) => {
+    setInternalGroupSharing = (group: Doc | { groupName: string }, permission: string, targetDoc?: Doc) => {
 
         const target = targetDoc || this.targetDoc!;
         const key = StrCast(group.groupName).replace(".", "_");
@@ -160,7 +160,7 @@ export class SharingManager extends React.Component<{}> {
             doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
             GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc);
 
-            if (key !== "Public") {
+            if (group instanceof Doc) {
                 const members: string[] = JSON.parse(StrCast(group.members));
                 const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
 
@@ -393,39 +393,22 @@ export class SharingManager extends React.Component<{}> {
     /**
      * @returns the main interface of the SharingManager.
      */
-    private get sharingInterface() {
-
+    @computed get sharingInterface() {
+        TraceMobx();
         const groupList = GroupManager.Instance?.getAllGroups() || [];
-        const sortedUsers = this.users.slice().sort(this.sortUsers)
-            .map(({ user: { email } }) => ({ label: email, value: indType + email }));
-        const sortedGroups = groupList.slice().sort(this.sortGroups)
-            .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
+        const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email }));
+        const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
 
         // the next block handles the users shown (individuals/groups/both)
         const options: GroupedOptions[] = [];
         if (GroupManager.Instance) {
             if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
-                options.push({
-                    label: 'Individuals',
-                    options: sortedUsers
-                },
-                    {
-                        label: 'Groups',
-                        options: sortedGroups
-                    });
-            }
-            else if (this.showUserOptions) {
-                options.push({
-                    label: 'Individuals',
-                    options: sortedUsers
-                });
-            }
-            else {
-                options.push({
-                    label: 'Groups',
-                    options: sortedGroups
-                });
+                options.push(
+                    { label: 'Individuals', options: sortedUsers },
+                    { label: 'Groups', options: sortedGroups });
             }
+            else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
+            else options.push({ label: 'Groups', options: sortedGroups });
         }
 
         const users = this.individualSort === "ascending" ? this.users.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
@@ -510,12 +493,10 @@ export class SharingManager extends React.Component<{}> {
                 ) : null
         );
 
-        // dummy doc for public acl
-        const publicDoc = new Doc;
-        publicDoc.groupName = "Public";
+
         // the list of groups shared with
-        const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true);
-        groupListMap.unshift(publicDoc);
+        const groupListMap: (Doc | { groupName: string })[] = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true);
+        groupListMap.unshift({ groupName: "Public" });
         const groupListContents = groupListMap.map(group => {
             const groupKey = `acl-${StrCast(group.groupName)}`;
             const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
@@ -527,7 +508,7 @@ export class SharingManager extends React.Component<{}> {
                     className={"container"}
                 >
                     <div className={"padding"}>{group.groupName}</div>
-                    {group.groupName !== "Public" ?
+                    {group instanceof Doc ?
                         (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
                             <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
                         </div>)
@@ -559,36 +540,6 @@ export class SharingManager extends React.Component<{}> {
                         onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)}
                     /> :
                     null}
-                {/* <p className={"share-link"}>Manage the public link to {this.focusOn("this document...")}</p>
-                {!this.linkVisible ? (null) :
-                    <div className={"link-container"}>
-                        <div className={"link-box"} onClick={this.copy}>{this.sharingUrl}</div>
-                        <div
-                            title={"Copy link to clipboard"}
-                            className={"copy"}
-                            style={{ backgroundColor: this.copied ? "lawngreen" : "gainsboro" }}
-                            onClick={this.copy}
-                        >
-                            <FontAwesomeIcon icon={"copy"} />
-                        </div>
-                    </div>
-                }
-                <div className={"people-with-container"}>
-                    {!this.linkVisible ? (null) : <p className={"people-with"}>People with this link</p>}
-                    <select
-                        className={"people-with-select"}
-                        value={this.sharingDoc ? StrCast(this.sharingDoc[PublicKey], SharingPermissions.None) : SharingPermissions.None}
-                        style={{
-                            marginLeft: this.linkVisible ? 10 : 0,
-                            color: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[PublicKey], SharingPermissions.None)) : DefaultColor,
-                            borderColor: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[PublicKey], SharingPermissions.None)) : DefaultColor
-                        }}
-                        onChange={e => this.setExternalSharing(e.currentTarget.value)}
-                    >
-                        {this.sharingOptions}
-                    </select>
-                </div>
-                <div className={"hr-substitute"} /> */}
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p>
                     <div className={"close-button"} onClick={this.close}>
@@ -660,16 +611,13 @@ export class SharingManager extends React.Component<{}> {
     }
 
     render() {
-        return (
-            <MainViewModal
-                contents={this.sharingInterface}
-                isDisplayed={this.isOpen}
-                interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
-                closeOnExternalClick={this.close}
-            />
-        );
+        return <MainViewModal
+            contents={this.sharingInterface}
+            isDisplayed={this.isOpen}
+            interactive={true}
+            dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+            overlayDisplayedOpacity={this.overlayOpacity}
+            closeOnExternalClick={this.close}
+        />;
     }
-
 }
\ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index ba9334c40..0c05a1b69 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -206,10 +206,12 @@ export class KeyManager {
                 preventDefault = false;
                 break;
             case "y":
+                SelectionManager.DeselectAll();
                 UndoManager.Redo();
                 stopPropagation = false;
                 break;
             case "z":
+                SelectionManager.DeselectAll();
                 UndoManager.Undo();
                 stopPropagation = false;
                 break;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e61b9624a..efbe5581b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -24,6 +24,8 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { TreeView } from "./TreeView";
 import React = require("react");
+import { DocumentManager } from '../../util/DocumentManager';
+import { FormattedTextBoxComment } from '../nodes/formattedText/FormattedTextBoxComment';
 
 export type collectionTreeViewProps = {
     treeViewHideTitle?: boolean;
@@ -61,19 +63,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         this.treedropDisposer?.();
     }
 
-    @action
-    remove = (doc: Doc | Doc[]): boolean => {
+    @undoBatch
+    remove = action((doc: Doc | Doc[]): boolean => {
         const docs = doc instanceof Doc ? [doc] : doc;
         const targetDataDoc = this.doc[DataSym];
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
         SelectionManager.DeselectAll();
         if (result.length !== value.length) {
+            const ind = targetDataDoc[this.props.fieldKey].indexOf(doc);
             targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+            if (ind > 0) {
+                const prev = targetDataDoc[this.props.fieldKey][ind - 1];
+                FormattedTextBox.SelectOnLoad = prev[Id];
+                const prevView = DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView);
+                prevView?.select(false);
+            }
             return true;
         }
         return false;
-    }
+    })
     @action
     addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
         const doAddDoc = (doc: Doc | Doc[]) =>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index e509eb78d..edb703135 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -121,12 +121,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
     @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
         this.props.treeView.props.select(false);
-        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+        const ind = this.dataDoc[key].indexOf(doc);
+        const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
+        return res;
     }
     @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc));
 
     constructor(props: any) {
         super(props);
+        console.log("Ttile = " + this.props.document.title);
         const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" });
         const openScript = ScriptField.MakeScript(`openOnRight(self)`);
         const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`);
@@ -218,7 +222,6 @@ export class TreeView extends React.Component<TreeViewProps> {
         })}
         onClick={() => {
             SelectionManager.DeselectAll();
-            Doc.UserDoc().activeSelection = new List([this.doc]);
             return false;
         }}
         OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))}
@@ -552,8 +555,9 @@ export class TreeView extends React.Component<TreeViewProps> {
                 : <div className={`treeView-container${selected ? "-active" : ""}`} ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}
                     onKeyDown={e => {
                         e.stopPropagation();
+                        e.preventDefault();
                         switch (e.key) {
-                            case "Backspace": return this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete");
+                            case "Backspace": return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
                             case "Enter": return UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet");
                             case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
                                 return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 771b6bbbe..41969fd3e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1162,6 +1162,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 FormattedTextBox.SelectOnLoadChar = "";
             } else if (curText?.Text) {
                 selectAll(this._editorView!.state, this._editorView?.dispatch);
+                this.startUndoTypingBatch();
             }
 
         }
@@ -1411,7 +1412,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     public startUndoTypingBatch() {
-        this._undoTyping = UndoManager.StartBatch("undoTyping");
+        !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping"));
     }
 
     public endUndoTypingBatch() {
@@ -1425,6 +1426,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static LiveTextUndo: UndoManager.Batch | undefined;
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
+        console.log("blur ", document.activeElement?.textContent);
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         this.endUndoTypingBatch();
         this.doLinkOnDeselect();
@@ -1473,9 +1475,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
         }
 
-        if (!this._undoTyping) {
-            this.startUndoTypingBatch();
-        }
+        this.startUndoTypingBatch();
     }
 
     ondrop = (eve: React.DragEvent) => {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ba7c9c7da..470f9d4be 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -24,6 +24,7 @@ import { LinkManager } from "../client/util/LinkManager";
 import JSZip = require("jszip");
 import { saveAs } from "file-saver";
 import { CollectionDockingView } from "../client/views/collections/CollectionDockingView";
+import { SelectionManager } from "../client/util/SelectionManager";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -1295,8 +1296,8 @@ Scripting.addGlobal(function docList(field: any) { return DocListCast(field); })
 Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
 Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 Scripting.addGlobal(function deiconifyView(doc: any) { Doc.deiconifyView(doc); });
-Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
-Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
+Scripting.addGlobal(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); });
+Scripting.addGlobal(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); });
 Scripting.addGlobal(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; });
 Scripting.addGlobal(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); });
 Scripting.addGlobal(function docCast(doc: FieldResult): any { return DocCastAsync(doc); });
@@ -1305,7 +1306,7 @@ Scripting.addGlobal(function activePresentationItem() {
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
 Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
-    const docs = DocListCast(Doc.UserDoc().activeSelection).
+    const docs = SelectionManager.SelectedDocuments().map(dv => dv.props.Document).
         filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCHOLDER && d.type !== DocumentType.KVP &&
             (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 9db79ced1..9e5890aa8 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -10,6 +10,8 @@ import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast, StrCast } from "./Types";
 import { returnZero } from "../Utils";
+import CursorField from "./CursorField";
+import { List } from "@material-ui/core";
 
 
 function _readOnlySetter(): never {
@@ -360,10 +362,12 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
             const oldValue = current;
             const newValue = ObjectField.MakeCopy(value);
             current = newValue;
-            UndoManager.AddEvent({
-                redo() { receiver[prop] = newValue; },
-                undo() { receiver[prop] = oldValue; }
-            });
+            if (!(value instanceof CursorField) && !(value?.some((v: any) => v instanceof CursorField))) {
+                UndoManager.AddEvent({
+                    redo() { receiver[prop] = newValue; },
+                    undo() { receiver[prop] = oldValue; }
+                });
+            }
         }
         target[Update](diff);
     };
-- 
cgit v1.2.3-70-g09d2


From 6fc12ef418f420087256a1a8463faac5dcce67c6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Sep 2020 00:22:16 -0400
Subject: made curPage a playground field so that scroll positions aren't
 mirrored.

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

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index aa74cc309..63672f960 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
         document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
 
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 756ff43f2..64e0eeb2c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -246,6 +246,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
 
     isChildActive = (outsideReaction?: boolean) => this._isChildActive;
     @computed get renderPdfView() {
+        TraceMobx();
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
             <div className="pdfBox-background"></div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41969fd3e..db4bf1086 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1426,7 +1426,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static LiveTextUndo: UndoManager.Batch | undefined;
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
-        console.log("blur ", document.activeElement?.textContent);
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         this.endUndoTypingBatch();
         this.doLinkOnDeselect();
-- 
cgit v1.2.3-70-g09d2


From a15ba9360bdb23e593ecc3985954549811cdd852 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Sep 2020 09:52:36 -0400
Subject: closed text box comment box when text box is unmounted.   preveted
 lozenge of which user had edited the box from lingering.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index db4bf1086..ff2ce90b7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1189,6 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.endUndoTypingBatch();
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this._editorView?.destroy();
+        FormattedTextBoxComment.Hide();
     }
 
     _downEvent: any;
-- 
cgit v1.2.3-70-g09d2


From 19d22f589c1ce0475b87d5c27859d3dde76c084b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Sep 2020 14:36:24 -0400
Subject: fixed text box llnk preview to go away when docs are closed.  fixed
 tree view to be more efficient by not re-rendering when items are selected. 
 made slide view text items forceActive

---
 .../views/collections/CollectionTreeView.scss      |  1 +
 .../views/collections/CollectionTreeView.tsx       | 44 +++++++-----
 src/client/views/collections/TreeView.tsx          | 21 +++---
 src/client/views/nodes/DocumentContentsView.tsx    | 19 ++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 82 +++++++++-------------
 .../formattedText/FormattedTextBoxComment.tsx      | 13 ++--
 6 files changed, 84 insertions(+), 96 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 20bfc0e9d..c5add7cfb 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -28,6 +28,7 @@
 
     .no-indent {
         padding-left: 0;
+        width: max-content;
     }
 
     .editableView-container {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index efbe5581b..e2247aec3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -41,7 +41,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     private _mainEle?: HTMLDivElement;
     public _uniqueId = Utils.GenerateGuid();
 
-    @computed get doc() { return this.props.Document; }
+    @computed get doc() { TraceMobx(); return this.props.Document; }
     @computed get dataDoc() { return this.props.DataDoc || this.doc; }
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
@@ -117,7 +117,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
 
     @computed get renderClearButton() {
-        return <div key="toolbar">
+        return !this.doc.allowClear ? (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>
@@ -192,37 +192,45 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); };
     active = (outsideReaction: boolean | undefined) => this.props.active(outsideReaction) || this._isChildActive;
-    render() {
+    @computed get treeChildren() {
+        TraceMobx();
+        return this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
+    }
+    @computed get treeViewElements() {
         TraceMobx();
-        if (!(this.doc instanceof Doc)) return (null);
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc);
-        const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
-        const childElements = childDocs && TreeView.GetChildElements(childDocs, this, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+        return TreeView.GetChildElements(this.treeChildren, this, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
             this.outerXf, this.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
             BoolCast(this.doc.treeViewPreventOpen), [], this.props.onCheckedClick,
             this.onChildClick, this.props.ignoreFields, true, this.whenActiveChanged);
+    }
+    @computed get titleBar() {
         const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle;
-        const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.doc.backgroundColor) || this.props.backgroundColor?.(this.doc, this.props.renderDepth);
+        return hideTitle ? (null) : (this.doc.treeViewOutlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren)
+    }
+    render() {
+        TraceMobx();
+        if (!(this.doc instanceof Doc)) return (null);
+        const background = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.doc.backgroundColor) || this.props.backgroundColor?.(this.doc, this.props.renderDepth);
+        const paddingX = `${NumCast(this.doc._xPadding, 10)}px`;
+        const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`;
+        const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined;
 
-        return !childDocs ? (null) : (
+        return !this.treeChildren ? (null) : (
             <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}>
                 <div className="collectionTreeView-dropTarget"
-                    style={{
-                        background: backgroundColor,
-                        paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`,
-                        paddingRight: `${NumCast(this.doc._xPadding, 10)}px`,
-                        paddingTop: `${NumCast(this.doc._yPadding, 20)}px`,
-                        pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined,
-                    }}
+                    style={{ background, paddingLeft: paddingX, paddingRight: paddingX, paddingTop, pointerEvents }}
                     onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                     onDrop={this.onTreeDrop}
                     ref={this.createTreeDropTarget}>
-                    {hideTitle ? (null) : (this.doc.treeViewOutlineMode ? this.documentTitle : this.editableTitle)(childDocs)}
-                    {this.doc.allowClear ? this.renderClearButton : (null)}
-                    <ul className="no-indent" style={{ width: "max-content" }} > {childElements} </ul>
+                    {this.titleBar}
+                    {this.renderClearButton}
+                    <ul className="no-indent">
+                        {this.treeViewElements}
+                    </ul>
                 </div >
             </div>
         );
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index edb703135..486e44f6b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -83,9 +83,9 @@ export class TreeView extends React.Component<TreeViewProps> {
     private _uniqueId = Utils.GenerateGuid();
     private _editMaxWidth: number | string = 0;
 
-    get doc() { return this.props.document; }
+    @computed get doc() { TraceMobx(); return this.props.document; }
     get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
-    get displayName() { return "TreeView(" + this.doc.title + ")"; }  // this makes mobx trace() statements more descriptive
+    get displayName() { return "TreeView(" + this.props.document.title + ")"; }  // this makes mobx trace() statements more descriptive
     get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; }
     get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); }
     get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); }
@@ -100,14 +100,14 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
     @computed get dataDoc() { return this.doc[DataSym]; }
     @computed get layoutDoc() { return Doc.Layout(this.doc); }
-    @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; }
+    @computed get fieldKey() { TraceMobx(); const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; }
     childDocList(field: string) {
         const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined;
         return ((this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
             (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
             DocListCastOrNull(this.doc[field])); // otherwise use the document's data field
     }
-    @computed get childDocs() { return this.childDocList(this.fieldKey); }
+    @computed get childDocs() { TraceMobx(); return this.childDocList(this.fieldKey); }
     @computed get childLinks() { return this.childDocList("links"); }
     @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); }
     @computed get boundsOfCollectionDocument() {
@@ -130,13 +130,12 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     constructor(props: any) {
         super(props);
-        console.log("Ttile = " + this.props.document.title);
         const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" });
         const openScript = ScriptField.MakeScript(`openOnRight(self)`);
         const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`);
         this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript);
         this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined;
-        if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
+        if (Doc.GetT(this.props.document, "editTitle", "string", true) === "*") Doc.SetInPlace(this.props.document, "editTitle", this._uniqueId, false);
     }
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
@@ -185,7 +184,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", forceActive: true, _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -659,8 +658,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         onChildClick: undefined | (() => ScriptField),
         ignoreFields: string[] | undefined,
         firstLevel: boolean,
-        whenActiveChanged: (isActive: boolean) => void
-    ) {
+        whenActiveChanged: (isActive: boolean) => void) {
         const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
@@ -691,7 +689,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
 
         const rowWidth = () => panelWidth() - 20;
-        return docs.map((child, i) => {
+        return docs.filter(child => child instanceof Doc).map((child, i) => {
             const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child);
             if (!pair.layout || pair.data instanceof Promise) {
                 return (null);
@@ -727,13 +725,12 @@ export class TreeView extends React.Component<TreeViewProps> {
                 const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0);
                 return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
             };
-            return !(child instanceof Doc) ? (null) : <TreeView
+            return <TreeView key={child[Id]}
                 document={pair.layout}
                 dataDoc={pair.data}
                 containingCollection={containingCollection}
                 prevSibling={docs[i]}
                 treeView={treeView}
-                key={child[Id]}
                 indentDocument={indent}
                 outdentDocument={!parentCollectionDoc ? undefined : outdent}
                 onCheckedClick={onCheckedClick}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 67e7c1986..e8c3662aa 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -114,22 +114,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
 }> {
     @computed get layout(): string {
         TraceMobx();
-        if (!this.layoutDoc) return "<p>awaiting layout</p>";
-        // const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");  // bcz: replaced this with below... is it right?
         if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
+        if (!this.layoutDoc) return "<p>awaiting layout</p>";
+        if (this.props.layoutKey === "layout_keyValue") return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
         const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
-        if (this.props.layoutKey === "layout_keyValue") {
-            return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
-        } else
-            if (layout === undefined) {
-                return this.props.Document.data ?
-                    "<FieldView {...props} fieldKey='data' />" :
-                    KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
-            } else if (typeof layout === "string") {
-                return layout;
-            } else {
-                return "<p>Loading layout</p>";
-            }
+        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
+        if (typeof layout === "string") return layout;
+        return "<p>Loading layout</p>";
     }
 
     get dataDoc() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ff2ce90b7..e92efd708 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1189,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.endUndoTypingBatch();
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this._editorView?.destroy();
-        FormattedTextBoxComment.Hide();
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
     }
 
     _downEvent: any;
@@ -1522,16 +1522,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
     render() {
         TraceMobx();
+        const selected = this.props.isSelected();
+        const active = this.active();
         const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground;
-        this.props.isSelected() && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props), 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
-        if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
-            setTimeout(() => FormattedTextBoxComment.Hide(), 0);
-        }
+        selected && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props)); // need to make sure that we update a text box that is selected after updating the one that was deselected
+        if (!selected && FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); }
         const minimal = this.props.ignoreAutoHeight;
-        const selPad = (this.props.isSelected() && !this.layoutDoc._singleLine) || minimal ? -10 : 0;
-        const selclass = this.props.isSelected() && !this.layoutDoc._singleLine ? "-selected" : "";
+        const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? -10 : 0;
+        const selclass = selected && !this.layoutDoc._singleLine ? "-selected" : "";
         return (
             <div className={"formattedTextBox-cont"} ref={this._boxRef}
                 style={{
@@ -1564,65 +1564,53 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     onMouseUp={this.onMouseUp}
                     onWheel={this.onPointerWheel}
                     onPointerEnter={action(() => this._entered = true)}
-                    onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
+                    onPointerLeave={action(e => {
                         this._entered = false;
                         const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
                         for (let child: any = target; child; child = child?.parentElement) {
-                            if (child === this._ref.current!) {
-                                this._entered = true;
-                            }
+                            child === this._ref.current! && (this._entered = true);
                         }
                     })}
                     onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
-                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
+                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active ? "none" : undefined }}
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) :
                                     `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
-                                pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
+                                pointerEvents: !active ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
-                    {!this.layoutDoc._showSidebar ? (null) : <>
-                        <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
-                            style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                            {this.sidebarWidthPercent === "0%" ? (null) :
-                                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-                                    PanelHeight={this.active() ? () => 1000 : this.props.PanelHeight}
-                                    PanelWidth={this.sidebarWidth}
-                                    scaleField={this.annotationKey + "-scale"}
-                                    annotationsKey={this.annotationKey}
-                                    isAnnotationOverlay={true}
-                                    focus={this.props.focus}
-                                    isSelected={this.props.isSelected}
-                                    select={emptyFunction}
-                                    active={this.annotationsActive}
-                                    ContentScaling={returnOne}
-                                    whenActiveChanged={this.whenActiveChanged}
-                                    removeDocument={this.removeDocument}
-                                    moveDocument={this.moveDocument}
-                                    addDocument={this.addDocument}
-                                    CollectionView={undefined}
-                                    ScreenToLocalTransform={this.sidebarScreenToLocal}
-                                    renderDepth={this.props.renderDepth + 1}
-                                    ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
-                            }
-                        </div>
-                        {this.props.isSelected() ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)}
-                    </>}
+                    {!this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
+                        <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                                PanelHeight={active ? () => 1000 : this.props.PanelHeight}
+                                PanelWidth={this.sidebarWidth}
+                                scaleField={this.annotationKey + "-scale"}
+                                annotationsKey={this.annotationKey}
+                                isAnnotationOverlay={true}
+                                focus={this.props.focus}
+                                isSelected={this.props.isSelected}
+                                select={emptyFunction}
+                                active={this.annotationsActive}
+                                ContentScaling={returnOne}
+                                whenActiveChanged={this.whenActiveChanged}
+                                removeDocument={this.removeDocument}
+                                moveDocument={this.moveDocument}
+                                addDocument={this.addDocument}
+                                CollectionView={undefined}
+                                ScreenToLocalTransform={this.sidebarScreenToLocal}
+                                renderDepth={this.props.renderDepth + 1}
+                                ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
+                        </div>}
+                    {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)}
                     {!this.layoutDoc._showAudio ? (null) :
                         <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
-                            <FontAwesomeIcon className="formattedTextBox-audioFont"
-                                style={{
-                                    color: this._recording ? "red" : "blue",
-                                    transitionDelay: "0.6s",
-                                    opacity: this._recording ? 1 : 0.25,
-                                }}
-                                icon={"microphone"} size="sm" />
+                            <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
                         </div>}
                 </div>
             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index a37210de6..14bbee1ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -143,9 +143,12 @@ export class FormattedTextBoxComment {
 
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
-        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
         FormattedTextBoxComment.linkDoc = undefined;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+        try {
+            ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+            FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
+        } catch (e) { }
     }
     public static SetState(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
@@ -228,17 +231,17 @@ export class FormattedTextBoxComment {
             if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
                 try {
                     ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+                    FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
                 } catch (e) { }
-                FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
                 FormattedTextBoxComment.tooltipText = document.createElement("div");
                 FormattedTextBoxComment.tooltipText.style.width = "100%";
                 FormattedTextBoxComment.tooltipText.style.height = "100%";
                 FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
                 FormattedTextBoxComment.tooltipText.style.cursor = "pointer";
-                FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-
                 FormattedTextBoxComment.tooltipText.textContent = "URL: " + href;
                 (FormattedTextBoxComment.tooltipText as any).href = href;
+                FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+
                 if (href.startsWith("https://en.wikipedia.org/wiki/")) {
                     wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
                 } else {
-- 
cgit v1.2.3-70-g09d2


From 70e0730e9319091604b772a705fffbb721bfa9df Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Sep 2020 00:37:05 -0400
Subject: fixed marquee summarize.  moved higlighint text to expoert mode. 
 moved create timestamp on enter to expert mode.

---
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 6 +++---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx       | 6 ++++--
 2 files changed, 7 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d04ef4ff9..e1ebb4eee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -525,12 +525,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         selected.map(d => {
             this.props.removeDocument(d);
-            d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
-            d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+            d.x = NumCast(d.x) - bounds.left;
+            d.y = NumCast(d.y) - bounds.top;
             d.page = -1;
             return d;
         });
-        const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+        const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
         const portal = Doc.MakeAlias(summary);
         Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
         Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e92efd708..1e6477f43 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -589,7 +589,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         const highlighting: ContextMenuProps[] = [];
-        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+        const noviceHighlighting = ["My Text", "Text from Others"];
+        const expertHighlighting = ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
+        (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
             highlighting.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
                     e.stopPropagation();
@@ -607,7 +609,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
         uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
         uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
-        uicontrols.push({ description: `Create TimeStamp When ${this.layoutDoc._timeStampOnEnter ? "Pause" : "Enter"}`, event: () => this.layoutDoc._timeStampOnEnter = !this.layoutDoc._timeStampOnEnter, icon: "expand-arrows-alt" });
+        !Doc.UserDoc().noviceMode && uicontrols.push({ description: `Create TimeStamp When ${this.layoutDoc._timeStampOnEnter ? "Pause" : "Enter"}`, event: () => this.layoutDoc._timeStampOnEnter = !this.layoutDoc._timeStampOnEnter, icon: "expand-arrows-alt" });
         !Doc.UserDoc().noviceMode && uicontrols.push({
             description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
                 proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
-- 
cgit v1.2.3-70-g09d2


From a6030259ccfc04b977bf3b9eca5dca0af1c8131f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Sep 2020 20:49:31 -0400
Subject: fixed sidebar collection to open up when clicking the handle.  fixed
 being able to type immediately into a new texst box in the text box sidebar.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 52 ++++++++++++----------
 1 file changed, 29 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1e6477f43..26902914a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -557,11 +557,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     sidebarDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction,
-            () => (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"));
+            () => this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")) !== "0%");
     }
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this.CurrentDiv.getBoundingClientRect();
         this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
+        this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
         return false;
     }
     @undoBatch
@@ -1518,6 +1519,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    return1000 = () => 1000;
+    @computed get sidebarCollection() {
+        return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
+            <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                    PanelHeight={this.active() ? this.return1000 : this.props.PanelHeight}
+                    PanelWidth={this.sidebarWidth}
+                    scaleField={this.annotationKey + "-scale"}
+                    annotationsKey={this.annotationKey}
+                    isAnnotationOverlay={true}
+                    focus={this.props.focus}
+                    isSelected={this.props.isSelected}
+                    select={emptyFunction}
+                    active={this.annotationsActive}
+                    ContentScaling={returnOne}
+                    whenActiveChanged={this.whenActiveChanged}
+                    removeDocument={this.removeDocument}
+                    moveDocument={this.moveDocument}
+                    addDocument={this.addDocument}
+                    CollectionView={undefined}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    renderDepth={this.props.renderDepth + 1}
+                    ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
+            </div>;
+    }
+
     @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
     sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
@@ -1587,28 +1614,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             }}
                         />
                     </div>
-                    {!this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
-                        <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-                                PanelHeight={active ? () => 1000 : this.props.PanelHeight}
-                                PanelWidth={this.sidebarWidth}
-                                scaleField={this.annotationKey + "-scale"}
-                                annotationsKey={this.annotationKey}
-                                isAnnotationOverlay={true}
-                                focus={this.props.focus}
-                                isSelected={this.props.isSelected}
-                                select={emptyFunction}
-                                active={this.annotationsActive}
-                                ContentScaling={returnOne}
-                                whenActiveChanged={this.whenActiveChanged}
-                                removeDocument={this.removeDocument}
-                                moveDocument={this.moveDocument}
-                                addDocument={this.addDocument}
-                                CollectionView={undefined}
-                                ScreenToLocalTransform={this.sidebarScreenToLocal}
-                                renderDepth={this.props.renderDepth + 1}
-                                ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
-                        </div>}
+                    {this.sidebarCollection}
                     {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)}
                     {!this.layoutDoc._showAudio ? (null) :
                         <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
-- 
cgit v1.2.3-70-g09d2


From 9ff10d73a1690c59d596cd5eb8138595af052a30 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 28 Sep 2020 13:34:06 -0400
Subject: removed sidebar menu item.  fixed contents of annotations to display
 annotation's info, not pdf's.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 -
 src/client/views/pdf/Annotation.tsx                       | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 26902914a..e7b4b72f4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -607,7 +607,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
 
         const uicontrols: ContextMenuProps[] = [];
-        uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
         uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
         uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
         !Doc.UserDoc().noviceMode && uicontrols.push({ description: `Create TimeStamp When ${this.layoutDoc._timeStampOnEnter ? "Pause" : "Enter"}`, event: () => this.layoutDoc._timeStampOnEnter = !this.layoutDoc._timeStampOnEnter, icon: "expand-arrows-alt" });
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 33c7bd4f3..a1b6c0547 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -141,7 +141,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
                 backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
             }} >
             {!this._showInfo ? (null) : <div className="pdfAnnotation-info">
-                {this.props.dataDoc.author + " " + Field.toString(this.props.dataDoc.creationDate as Field)}
+                {this.props.anno.author + " " + Field.toString(this.props.anno.creationDate as Field)}
             </div>}
         </div>);
     }
-- 
cgit v1.2.3-70-g09d2


From 9ad43c9e2a068fdf6308339f6f82e29cf94d2c14 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Sep 2020 02:46:28 -0400
Subject: changed sidebar functionality for textboxes to open outward and make
 handle blue when there are contents.  added a fitToBox capability for
 contents of sidebar.

---
 src/client/views/DocumentButtonBar.tsx             |  4 +--
 src/client/views/PropertiesButtons.tsx             |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 36 ++++++++++++----------
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 13 +++++---
 src/client/views/pdf/Annotation.tsx                |  8 ++---
 src/client/views/pdf/PDFViewer.tsx                 |  4 +--
 9 files changed, 40 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c5fd3c777..5ec43319b 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -363,9 +363,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             {![DocumentType.VID, DocumentType.WEB].includes(StrCast(this.view0.props.Document.type) as DocumentType) ? (null) : <div className="documentButtonBar-button">
                 {this.annotateButton}
             </div>}
-            <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
+            {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
                 {this.considerGoogleDocsPush}
-            </div>
+            </div>}
             <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
                 {this.considerGoogleDocsPull}
             </div>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 462091f63..9de4f9c67 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -529,7 +529,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             <div className="propertiesButtons-button" style={{ display: !isFreeForm ? "none" : "" }}>
                 {this.clustersButton}
             </div>
-            <div className="propertiesButtons-button" style={{ display: !isFreeForm ? "none" : "" }}>
+            <div className="propertiesButtons-button" style={{ display: !isFreeForm && !isText ? "none" : "" }}>
                 {this.fitContentButton}
             </div>
             <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4ee00e0f4..423d39951 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -176,7 +176,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
             smoothScroll(doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
         }
-        afterFocus && setTimeout(() => afterFocus?.(), 500);
+        afterFocus && setTimeout(afterFocus, 500);
     }
 
     getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 072cd23db..06c35de03 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -664,7 +664,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         ignoreFields: string[] | undefined,
         firstLevel: boolean,
         whenActiveChanged: (isActive: boolean) => void,
-        dontRegisterView: boolean | undeifned) {
+        dontRegisterView: boolean | undefined) {
         const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 75ad8e5b0..967055638 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -104,28 +104,32 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @observable canPanY: boolean = true;
 
     @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
-    @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
-    @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
+    @computed get fitToContent() { return this.props.fitToBox || (this.Document._fitToBox && !this.isAnnotationOverlay); }
+    @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : returnVal(this.props.NativeWidth?.(), NumCast(this.Document._nativeWidth)); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : returnVal(this.props.NativeHeight?.(), NumCast(this.Document._nativeHeight)); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
-    private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
-    private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
-        Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
-            this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
-        NumCast(this.Document[this.scaleFieldKey], 1))
-
+    private panX = () => this.fitToContent && !this.props.isAnnotationOverlay ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
+    private panY = () => this.fitToContent && !this.props.isAnnotationOverlay ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
+    private zoomScaling = () => {
+        const mult = this.fitToContentScaling / this.parentScaling;
+        if (this.fitToContent) {
+            const zs = !this.childDocs.length ? 1 :
+                Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x));
+            return mult * (this.props.isAnnotationOverlay ? Math.min(zs, 1) : zs);
+        }
+        return mult * NumCast(this.Document[this.scaleFieldKey], 1);
+    }
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
-        return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0;  // shift so pan position is at center of window for non-overlay collections
+        return !this.props.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0;  // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
-        return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
+        return !this.props.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedGetLocalTransform(): Transform {
         return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
@@ -776,9 +780,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
         let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
-        if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
-            deltaScale = 1 / this.zoomScaling();
-        }
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.getTransform().transformPoint(pointX, pointY);
         const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
@@ -1106,7 +1107,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         });
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
-        const elements: ViewDefResult[] = computedElementData.slice();
+        const elements = computedElementData.slice();
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
@@ -1127,8 +1128,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
-        if (this.props.isAnnotationOverlay) {
-            this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
+        if (this.props.isAnnotationOverlay) {   // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
+            if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, NumCast(this.props.Document[this.scaleFieldKey], 1));
+            else this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
         }
 
         this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 61f01cc95..a7b534956 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -458,7 +458,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         TraceMobx();
         return <div className="webBox-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight) }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+                <Annotation {...this.props} showInfo={emptyFunction} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
             }
         </div>;
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e7b4b72f4..a0af8d60f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -557,7 +557,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     sidebarDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction,
-            () => this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")) !== "0%");
+            action(() => {
+                const prevWidth = this.sidebarWidth();
+                this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+                this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+            }));
     }
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this.CurrentDiv.getBoundingClientRect();
@@ -1518,16 +1522,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
-    return1000 = () => 1000;
     @computed get sidebarCollection() {
+        const fitToBox = this.props.Document._fitToBox;
         return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
             <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                 <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-                    PanelHeight={this.active() ? this.return1000 : this.props.PanelHeight}
+                    PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.sidebarWidth}
                     scaleField={this.annotationKey + "-scale"}
                     annotationsKey={this.annotationKey}
                     isAnnotationOverlay={true}
+                    fitToBox={fitToBox}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
                     select={emptyFunction}
@@ -1614,7 +1619,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         />
                     </div>
                     {this.sidebarCollection}
-                    {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)}
+                    {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))`, background: DocListCast(this.dataDoc[this.annotationKey]).length ? "lightBlue" : undefined }} onPointerDown={this.sidebarDown} /> : (null)}
                     {!this.layoutDoc._showAudio ? (null) :
                         <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
                             <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index b7c7dae38..84b14cd61 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,7 +1,7 @@
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Field } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym, Field, Opt } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
 import { Cast, FieldValue, BoolCast, NumCast, StrCast, PromiseValue } from "../../../fields/Types";
@@ -17,7 +17,7 @@ interface IAnnotationProps {
     focus: (doc: Doc) => void;
     dataDoc: Doc;
     fieldKey: string;
-    showInfo: (anno: Doc) => void;
+    showInfo: (anno: Opt<Doc>) => void;
 }
 
 @observer
@@ -25,7 +25,7 @@ export
     class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return DocListCast(this.props.anno.annotations).map(a =>
-            <RegionAnnotation {...this.props} pinToPres={this.props.pinToPres} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
+            <RegionAnnotation {...this.props} showInfo={this.props.showInfo} pinToPres={this.props.pinToPres} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
     }
 }
 
@@ -40,7 +40,7 @@ interface IRegionAnnotationProps {
     document: Doc;
     dataDoc: Doc;
     fieldKey: string;
-    showInfo: (anno: Doc) => void;
+    showInfo: (anno: Opt<Doc>) => void;
 }
 
 @observer
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e54464b77..fb87da744 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -703,10 +703,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
                     {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
                 </div>
-            </div>
+            </div>;
     }
 
-    showInfo = action((anno: Doc) => this._overlayAnnoInfo = anno);
+    showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
     panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
     panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
-- 
cgit v1.2.3-70-g09d2


From 60c930b0850f87a88f032ddc7029fa4586251ae8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Sep 2020 11:31:41 -0400
Subject: fixed highlighting of link hypertext anchors (was offset very wrongly
 before). fixed setting link mark correctly when linking two hypertext
 anchors.  fixed allowing link to parent collection when parent collection is
 specified by clicking its link button

---
 src/client/documents/Documents.ts                         |  4 ++--
 src/client/views/nodes/DocumentLinksButton.tsx            |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 ++++++++------
 3 files changed, 11 insertions(+), 9 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1d7e4f386..19f719c43 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -977,9 +977,9 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
+    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
-        if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
+        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
         const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 0cb5b94f4..8b48acf23 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -161,7 +161,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             DocumentLinksButton.AnnotationUri = undefined;
             //!this.props.StartLink 
         } else if (startLink !== endLink) {
-            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
+            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
             // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks ().  see note above in onLInkButtonMoved
             if (endLinkView) {
                 endLinkView._link = linkDoc;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0af8d60f..12fc690c8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -825,8 +825,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             () => this.props.makeLink?.(),
             (linkDoc: Opt<Doc>) => {
                 if (linkDoc) {
-                    const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
-                    const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
+                    const a1 = Cast(linkDoc.anchor1, Doc, null);
+                    const a2 = Cast(linkDoc.anchor2, Doc, null);
+                    const otherAnchor = Doc.AreProtosEqual(a1, this.rootDoc) ? a2 : a1;
+                    const anchor2Title = StrCast(otherAnchor.title, "-untitled-");
+                    const anchor2Id = otherAnchor?.[Id] || "";
                     this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
                 }
             },
@@ -910,15 +913,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             async (scrollToLinkID) => {
                 const findLinkFrag = (frag: Fragment, editor: EditorView) => {
                     const nodes: Node[] = [];
-                    let offset = 0;
                     frag.forEach((node, index) => {
                         const examinedNode = findLinkNode(node, editor);
                         if (examinedNode?.textContent) {
                             nodes.push(examinedNode);
-                            offset = index;
+                            !start && (start = index);
                         }
                     });
-                    return { frag: Fragment.fromArray(nodes), start: start + offset };
+                    return { frag: Fragment.fromArray(nodes), start };
                 };
                 const findLinkNode = (node: Node, editor: EditorView) => {
                     if (!node.isText) {
@@ -930,7 +932,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
                 };
 
-                const start = 0;
+                let start = 0;
                 if (this._editorView && scrollToLinkID) {
                     const editor = this._editorView;
                     const ret = findLinkFrag(editor.state.doc.content, editor);
-- 
cgit v1.2.3-70-g09d2


From 1cb5ac5e07ad90589670a788e3144d9d9581a393 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Sep 2020 15:03:16 -0400
Subject: made pdf/web anchors pushpins by default. can now remove docs you own
 from sidebars you don't.   made formatted text boxes alway show sidebar
 handle when there's content.  sidebar text has no padding by default. sidebar
 collection doesn't scroll when text overflows. added more playground fields

---
 src/client/documents/Documents.ts                  | 21 ++++++++++++
 src/client/util/CurrentUserUtils.ts                |  3 +-
 src/client/views/DocComponent.tsx                  |  3 +-
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 ++++
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/FilterBox.tsx               |  2 +-
 src/client/views/nodes/WebBox.tsx                  |  1 +
 .../nodes/formattedText/FormattedTextBox.scss      | 18 +++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 40 ++++++++++++++++------
 src/client/views/pdf/PDFViewer.tsx                 |  3 +-
 12 files changed, 76 insertions(+), 26 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 19f719c43..ad30e7d33 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -889,6 +889,27 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
+    export function Excluded(d: Doc, docFilters: string[]) {
+        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
+        for (let i = 0; i < docFilters.length; i += 3) {
+            const [key, value, modifiers] = docFilters.slice(i, i + 3);
+            if (!filterFacets[key]) {
+                filterFacets[key] = {};
+            }
+            filterFacets[key][value] = modifiers;
+        }
+
+        if (d.z) return false;
+        for (const facetKey of Object.keys(filterFacets)) {
+            const facet = filterFacets[facetKey];
+            const xs = Object.keys(facet).filter(value => facet[value] === "x");
+            const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value));
+            if (failsNotEqualFacets) {
+                return true;
+            }
+        }
+        return false;
+    }
     export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) {
         const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 342954a4e..3e76b189a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1127,8 +1127,9 @@ export class CurrentUserUtils {
         CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
     }
 
-    public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number) {
+    public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean) {
         const tbox = Docs.Create.TextDocument("", {
+            _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined,
             _width: width || 200, _height: height || 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
             _fontFamily: StrCast(Doc.UserDoc().fontFamily), title
         });
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b3c90a68f..893b74d75 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -123,7 +123,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         @action.bound
         removeDocument(doc: Doc | Doc[]): boolean {
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
-            if (effectiveAcl === AclAdmin || effectiveAcl === AclEdit) {
+            const docAcl = GetEffectiveAcl(doc);
+            if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || docAcl === AclAdmin) {
                 const docs = doc instanceof Doc ? [doc] : doc;
                 docs.map(doc => doc.isPushpin = doc.annotationOn = undefined);
                 const targetDataDoc = this.dataDoc;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4299dba86..5469c2358 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
         document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
 
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 26fe00657..649b8c175 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -142,6 +142,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
             const docsforFilter: Doc[] = [];
             childDocs.forEach((d) => {
+                if (DocUtils.Excluded(d, docFilters)) return;
                 let notFiltered = d.z || ((!searchDocs.length || searchDocs.includes(d)) && ((!docFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript).length > 0));
                 const fieldKey = Doc.LayoutFieldKey(d);
                 const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 967055638..a3f76839d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1625,6 +1625,12 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const pany = -this.props.panY();
         const zoom = this.props.zoomScaling();
         return <div className={freeformclass}
+            onScroll={e => {
+                const target = e.target as any;
+                if (getComputedStyle(target.parentElement)?.overflow === "visible") {  // if collection is visible, then scrolling will mess things up since there are no scroll bars
+                    target.scrollTop = target.scrollLeft = 0;
+                }
+            }}
             style={{
                 transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`,
                 transition: this.props.transition,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e1ebb4eee..7c64fd429 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -141,7 +141,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             } else if (!e.ctrlKey && !e.metaKey) {
                 FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.ChildLayoutString ? e.key : "";
                 FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
-                this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100));
+                this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0));
                 e.stopPropagation();
             }
     }
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index c1978e331..d5b50122b 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -48,7 +48,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
 
         const keys = new Set<string>(noviceFields);
         this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
-        return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("acl")) || noviceFields.includes(key)).sort();
+        return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort();
     }
 
     gatherFieldValues(dashboard: Doc, facetKey: string) {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a7b534956..421aac69f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -508,6 +508,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) {
                         e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true;
+                        annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document;
                     }
                 }
             });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index d1109b388..dbf98a5e9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -106,6 +106,15 @@
         border-width: 1px;
     }
 }
+.formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner-selected {
+    .ProseMirror {
+        padding:10px;
+    }
+    .ProseMirror:hover {
+        background: unset;
+    }
+}
 
 // .menuicon {
 //     display: inline-block;
@@ -332,15 +341,6 @@ footnote::after {
     .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; vertical-align: top; margin-left: -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
 }
 
-.formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner-selected {
-    .ProseMirror {
-        padding:10px;
-    }
-    .ProseMirror:hover {
-        background: unset;
-    }
-}
 
 @media only screen and (max-width: 1000px) {
     @import "../../globalCssVariables";
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 12fc690c8..1148087c6 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -557,11 +557,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     sidebarDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction,
-            action(() => {
+            () => setTimeout(action(() => {
                 const prevWidth = this.sidebarWidth();
                 this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
                 this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
-            }));
+            })), false);
     }
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this.CurrentDiv.getBoundingClientRect();
@@ -1524,6 +1524,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    @computed get audioHandle() {
+        return !this.layoutDoc._showAudio ? (null) :
+            <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
+                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
+            </div>;
+    }
+
+    @computed get sidebarHandle() {
+        const annotated = DocListCast(this.dataDoc[this.annotationKey]).length;
+        return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
+            <div className="formattedTextBox-sidebar-handle"
+                style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightBlue" : undefined }}
+                onPointerDown={this.sidebarDown}
+                onClick={e => {
+                    console.log(e);
+                }}
+            />;
+    }
+
     @computed get sidebarCollection() {
         const fitToBox = this.props.Document._fitToBox;
         return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
@@ -1531,6 +1550,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.sidebarWidth}
+                    xMargin={0}
                     scaleField={this.annotationKey + "-scale"}
                     annotationsKey={this.annotationKey}
                     isAnnotationOverlay={true}
@@ -1565,8 +1585,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         selected && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props)); // need to make sure that we update a text box that is selected after updating the one that was deselected
         if (!selected && FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); }
         const minimal = this.props.ignoreAutoHeight;
-        const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? -10 : 0;
-        const selclass = selected && !this.layoutDoc._singleLine ? "-selected" : "";
+        const margins = NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0);
+        const selPad = Math.min(margins, 10);
+        const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
+        const selclass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
         return (
             <div className={"formattedTextBox-cont"} ref={this._boxRef}
                 style={{
@@ -1614,18 +1636,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
-                                padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) :
-                                    `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+                                padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`,
                                 pointerEvents: !active ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
                     {this.sidebarCollection}
-                    {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))`, background: DocListCast(this.dataDoc[this.annotationKey]).length ? "lightBlue" : undefined }} onPointerDown={this.sidebarDown} /> : (null)}
-                    {!this.layoutDoc._showAudio ? (null) :
-                        <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
-                            <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
-                        </div>}
+                    {this.sidebarHandle}
+                    {this.audioHandle}
                 </div>
             </div>
         );
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index fb87da744..77dd40f2a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -642,6 +642,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                         e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                     }
                     annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing?
+                    annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document;
                     e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument });
                 }
             });
@@ -733,7 +734,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
                 addDocument={this.addDocument}
-                docFilters={this.props.docRangeFilters}
+                docFilters={this.props.docFilters}
                 docRangeFilters={this.props.docRangeFilters}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
-- 
cgit v1.2.3-70-g09d2


From 7e5779c7404d92fdd64958d6240a1418a2ee57d7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Sep 2020 20:37:26 -0400
Subject: simplfiled ':' menu and fixed creating items to not assume a
 template.  add emptyNote.  fixed scrolling in stacking views broken in
 previous commit.  changed formattedTextBox to support stacking sidebar.

---
 src/client/documents/Documents.ts                  | 19 +++---
 src/client/util/CurrentUserUtils.ts                |  7 ++-
 .../views/collections/CollectionStackingView.tsx   | 36 ++++++-----
 .../CollectionStackingViewFieldColumn.tsx          | 31 +++++-----
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 69 +++++++++++++---------
 7 files changed, 97 insertions(+), 69 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ad30e7d33..47fa7067d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,8 @@
 import { runInAction } from "mobx";
 import { basename, extname } from "path";
 import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclAddonly } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
 import { HtmlField } from "../../fields/HtmlField";
 import { InkField } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -11,6 +12,7 @@ import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
 import { ComputedField, ScriptField } from "../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
+import { SharingPermissions } from "../../fields/util";
 import { MessageStore } from "../../server/Message";
 import { Upload } from "../../server/SharedMediaTypes";
 import { OmitKeys, Utils } from "../../Utils";
@@ -33,6 +35,7 @@ import { AudioBox } from "../views/nodes/AudioBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
 import { DocHolderBox } from "../views/nodes/DocHolderBox";
+import { FilterBox } from "../views/nodes/FilterBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
@@ -50,9 +53,6 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
-import { FilterBox } from "../views/nodes/FilterBox";
-import { SharingPermissions } from "../../fields/util";
-import { SharingManager } from "../util/SharingManager";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -208,6 +208,8 @@ export interface DocumentOptions {
     treeViewOutlineMode?: boolean; // whether slide should function as a text outline
     treeViewLockExpandedView?: boolean; // whether the expanded view can be changed
     treeViewDefaultExpandedView?: string; // default expanded view
+    sidebarColor?: string;  // background color of text sidebar
+    sidebarViewType?: string; // collection type of text sidebar
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
@@ -1094,8 +1096,8 @@ export namespace DocUtils {
         return ctor ? ctor(path, options) : undefined;
     }
 
-    export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
-        ContextMenu.Instance.addItem({
+    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 ...",
             subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
                 description: ":" + StrCast(note.title),
@@ -1114,14 +1116,15 @@ export namespace DocUtils {
         });
         ContextMenu.Instance.addItem({
             description: "Add Template Doc ...",
-            subitems: DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({
+            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.ApplyTemplate(dragDoc);
+                    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);
                     }
                 }),
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3e76b189a..7cc35d67a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -422,9 +422,13 @@ export class CurrentUserUtils {
             doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) });
         }
         if (doc.emptyAudio === undefined) {
-            doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true, cloneFieldFilter: new List<string>(["system"]) });
+            doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
             ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
         }
+        if (doc.emptyNote === undefined) {
+            doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", system: true, cloneFieldFilter: new List<string>(["system"]) });
+            ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0;
+        }
         if (doc.emptyImage === undefined) {
             doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true });
         }
@@ -444,6 +448,7 @@ export class CurrentUserUtils {
             this.setupActiveMobileMenu(doc);
         }
         return [
+            { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, },
             { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, },
             { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
             { 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, noviceMode: true },
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 423d39951..daaee67dc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -33,8 +33,12 @@ const _global = (window /* browser */ || global /* node */) as any;
 type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
 const StackingDocument = makeInterface(collectionSchema, documentSchema);
 
+export type collectionStackingViewProps = {
+    chromeStatus?: string;
+};
+
 @observer
-export class CollectionStackingView extends CollectionSubView(StackingDocument) {
+export class CollectionStackingView extends CollectionSubView<StackingDocument, Partial<collectionStackingViewProps>>(StackingDocument) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _pivotFieldDisposer?: IReactionDisposer;
@@ -44,15 +48,16 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
+    @computed get chromeStatus() { return this.props.chromeStatus || StrCast(this.layoutDoc._chromeStatus); }
     @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField)); }
     @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
     @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
-    @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 5)); } // 2 * this.gridGap)); }
+    @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, this.props.yMargin || NumCast(this.layoutDoc._yMargin, 5)); } // 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
     @computed get isStackingView() { return BoolCast(this.layoutDoc._columnsStack, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.pivotField && (this.layoutDoc._chromeStatus !== 'view-mode' && this.layoutDoc._chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() / this.props.ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
@@ -354,7 +359,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
-                            Doc.Layout(doc)._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
+                            const height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
+                            Doc.Layout(doc)._height = Math.max(height, NumCast(doc[this.props.fieldKey + "-height"]));
                         }
                     }));
                     this.observer.observe(ref);
@@ -406,7 +412,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
-                            Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
+                            const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
+                            Doc.Layout(doc)._height = Math.max(height, NumCast(doc[this.props.fieldKey + "-height"]));
                         }
                     }));
                     this.observer.observe(ref);
@@ -444,10 +451,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
     }
 
-    onToggle = (checked: Boolean) => {
-        this.layoutDoc._chromeStatus = checked ? "collapsed" : "view-mode";
-    }
-
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped()) {
@@ -504,13 +507,14 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                             style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                             <EditableView {...editableViewProps} />
                         </div>}
-                    {this.layoutDoc._chromeStatus !== 'disabled' && this.props.isSelected() ? <Switch
-                        onChange={this.onToggle}
-                        onClick={this.onToggle}
-                        defaultChecked={this.layoutDoc._chromeStatus !== 'view-mode'}
-                        checkedChildren="edit"
-                        unCheckedChildren="view"
-                    /> : null}
+                    {/* {this.chromeStatus === 'disabled' || !this.props.isSelected() ? (null) :
+                        <Switch
+                            onChange={this.onToggle}
+                            onClick={this.onToggle}
+                            defaultChecked={this.chromeStatus !== 'view-mode'}
+                            checkedChildren="edit"
+                            unCheckedChildren="view"
+                        />} */}
                 </div> </div>
         );
     }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 44d264b36..1bc989e83 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -22,6 +22,8 @@ import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { CollectionStackingView } from "./CollectionStackingView";
 import "./CollectionStackingView.scss";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
+import { Id } from "../../../fields/FieldSymbols";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -137,8 +139,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
         const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
-        this.props.parent.props.addDocument(newDoc);
-        return false;
+        FormattedTextBox.SelectOnLoad = newDoc[Id];
+        FormattedTextBox.SelectOnLoadChar = " ";
+        return this.props.parent.props.addDocument(newDoc);
     }
 
     @action
@@ -228,7 +231,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const docItems: ContextMenuProps[] = [];
         const dataDoc = this.props.parent.props.DataDoc || this.props.parent.Document;
 
-        DocUtils.addDocumentCreatorMenuItems(this.props.parent.props.addDocument, this.props.parent.props.addDocument, x, y);
+        DocUtils.addDocumentCreatorMenuItems((doc) => {
+            FormattedTextBox.SelectOnLoad = doc[Id];
+            return this.props.parent.props.addDocument(doc);
+        }, this.props.parent.props.addDocument, x, y, true);
 
         Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
             docItems.push({
@@ -256,13 +262,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     }
                 }, icon: "compress-arrows-alt"
             }));
-        layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
-        layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200 })), icon: "compress-arrows-alt" });
-        layoutItems.push({ description: ":columns", event: () => this.props.parent.props.addDocument(Docs.Create.MulticolumnDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
-        layoutItems.push({ description: ":image", event: () => this.props.parent.props.addDocument(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
-
-        ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
-        ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
+        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
+        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
         ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
             Doc.GetProto(this.props.parent.props.Document)[name] = "";
             const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
@@ -285,7 +286,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const heading = this._heading;
         const style = this.props.parent;
         const singleColumn = style.isStackingView;
-        const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin, 5);
+        const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.yMargin, 5);
         const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         const evContents = heading ? heading : this.props?.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
         const headerEditableViewProps = {
@@ -306,10 +307,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const headingView = this.props.headingObject ?
             <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
                 style={{
-                    marginTop: NumCast(this.props.parent.props.Document._yMargin, 5),
+                    marginTop: NumCast(this.props.parent.yMargin, 5),
                     width: (style.columnWidth) /
                         ((uniqueHeadings.length +
-                            ((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+                            ((this.props.parent.chromeStatus !== 'view-mode' && this.props.parent.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
                 }}>
                 <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -342,7 +343,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 </div>
             </div> : (null);
         for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
-        const chromeStatus = this.props.parent.props.Document._chromeStatus;
+        const chromeStatus = this.props.parent.chromeStatus;
         const type = this.props.parent.props.Document.type;
         return <>
             {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
@@ -365,7 +366,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                         </div>
                         {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ?
                             <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
-                                style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 70 }}>
+                                style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 10 }}>
                                 <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
                             </div> : null}
                     </div>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 06c35de03..1dfa5615a 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -185,7 +185,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", forceActive: true, _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 });
+        const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", "sidebarColor": "transparent", "sidebarViewType": CollectionViewType.Freeform, forceActive: true, _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 });
         Doc.GetProto(bullet).layout = CollectionView.LayoutString("data");
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a3f76839d..89e0450d7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1627,7 +1627,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         return <div className={freeformclass}
             onScroll={e => {
                 const target = e.target as any;
-                if (getComputedStyle(target.parentElement)?.overflow === "visible") {  // if collection is visible, then scrolling will mess things up since there are no scroll bars
+                if (getComputedStyle(target)?.overflow === "visible") {  // if collection is visible, then scrolling will mess things up since there are no scroll bars
                     target.scrollTop = target.scrollLeft = 0;
                 }
             }}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1148087c6..93eb94854 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
+import { isEqual, max } from "lodash";
 import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap, selectAll } from "prosemirror-commands";
@@ -58,6 +58,8 @@ import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
 import React = require("react");
 import { DocumentManager } from '../../../util/DocumentManager';
+import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionViewType } from '../../collections/CollectionView';
 
 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
@@ -1166,7 +1168,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
                 const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
                 const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
-                this._editorView.dispatch(this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar).setStoredMarks(storedMarks));
+                const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
+                this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
                 FormattedTextBox.SelectOnLoadChar = "";
             } else if (curText?.Text) {
                 selectAll(this._editorView!.state, this._editorView?.dispatch);
@@ -1496,6 +1499,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @action
     tryUpdateHeight(limitHeight?: number) {
         let scrollHeight = this.ProseRef?.scrollHeight || 0;
+        this.rootDoc[this.fieldKey + "-height"] = 0;
         if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1);
             if (limitHeight && scrollHeight > limitHeight) {
@@ -1517,8 +1521,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 try {
                     const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", ""));
                     const outer = this.rootDoc[HeightSym]() - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0);
-                    this.rootDoc._height = newHeight + Math.max(0, outer);
-                    this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
+                    const finalHeight = newHeight + Math.max(0, outer);
+                    const maxsidebar = !this.sidebarWidth() ? 0 : Array.from(this._boxRef.current!.getElementsByClassName("collectionStackingViewFieldColumn")).reduce((prev, ele) => Math.max(prev, Number(getComputedStyle(ele).height.replace("px", ""))), 0);
+                    if (this.rootDoc._height !== finalHeight && finalHeight > maxsidebar) {
+                        this.rootDoc._height = finalHeight;
+                        this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined;
+                    }
+                    this.rootDoc[this.fieldKey + "-height"] = finalHeight;
                 } catch (e) { console.log("Error in tryUpdateHeight"); }
             }
         }
@@ -1545,36 +1554,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @computed get sidebarCollection() {
         const fitToBox = this.props.Document._fitToBox;
+        const collectionProps = {
+            ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit,
+            PanelHeight: this.props.PanelHeight,
+            PanelWidth: this.sidebarWidth,
+            xMargin: 0,
+            yMargin: 0,
+            chromeStatus: "enabled",
+            scaleField: this.annotationKey + "-scale",
+            annotationsKey: this.annotationKey,
+            isAnnotationOverlay: true,
+            fitToBox: fitToBox,
+            focus: this.props.focus,
+            isSelected: this.props.isSelected,
+            select: emptyFunction,
+            active: this.annotationsActive,
+            ContentScaling: returnOne,
+            whenActiveChanged: this.whenActiveChanged,
+            removeDocument: this.removeDocument,
+            moveDocument: this.moveDocument,
+            addDocument: this.addDocument,
+            CollectionView: undefined,
+            ScreenToLocalTransform: this.sidebarScreenToLocal,
+            renderDepth: this.props.renderDepth + 1,
+            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
+        };
         return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
-            <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
-                    PanelHeight={this.props.PanelHeight}
-                    PanelWidth={this.sidebarWidth}
-                    xMargin={0}
-                    scaleField={this.annotationKey + "-scale"}
-                    annotationsKey={this.annotationKey}
-                    isAnnotationOverlay={true}
-                    fitToBox={fitToBox}
-                    focus={this.props.focus}
-                    isSelected={this.props.isSelected}
-                    select={emptyFunction}
-                    active={this.annotationsActive}
-                    ContentScaling={returnOne}
-                    whenActiveChanged={this.whenActiveChanged}
-                    removeDocument={this.removeDocument}
-                    moveDocument={this.moveDocument}
-                    addDocument={this.addDocument}
-                    CollectionView={undefined}
-                    ScreenToLocalTransform={this.sidebarScreenToLocal}
-                    renderDepth={this.props.renderDepth + 1}
-                    ContainingCollectionDoc={this.props.ContainingCollectionDoc} />
+            <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
+                style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? <CollectionFreeFormView {...collectionProps} /> : <CollectionStackingView {...collectionProps} />}
             </div>;
     }
 
     @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
     sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
-    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
+    @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
     render() {
         TraceMobx();
         const selected = this.props.isSelected();
-- 
cgit v1.2.3-70-g09d2


From cb18f848b44705a25f786f9a0e678095101f9e01 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Sep 2020 21:28:33 -0400
Subject: fixed highlighting of sidebar handle when no annotations are
 accessible

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 93eb94854..15e35ce72 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1541,7 +1541,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     @computed get sidebarHandle() {
-        const annotated = DocListCast(this.dataDoc[this.annotationKey]).length;
+        const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.title).length;
         return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
             <div className="formattedTextBox-sidebar-handle"
                 style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightBlue" : undefined }}
-- 
cgit v1.2.3-70-g09d2


From be8940e0fea4cf2082c444d54173ee7482e8a327 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 30 Sep 2020 23:17:37 -0400
Subject: fixed linking to text hyperlinks from PDFs

---
 src/client/views/GlobalKeyHandler.ts                               | 6 +++---
 .../collections/collectionFreeForm/CollectionFreeFormLinkView.tsx  | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx          | 7 ++++---
 3 files changed, 8 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 9ca1dbf11..1b2395423 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -52,7 +52,7 @@ export class KeyManager {
 
     public unhandle = action((e: KeyboardEvent) => {
         if (e.key.toLowerCase() === "shift") KeyManager.Instance.ShiftPressed = false;
-    })
+    });
 
     public handle = action(async (e: KeyboardEvent) => {
         if (e.key.toLowerCase() === "shift") KeyManager.Instance.ShiftPressed = true;
@@ -75,7 +75,7 @@ export class KeyManager {
 
         control.stopPropagation && e.stopPropagation();
         control.preventDefault && e.preventDefault();
-    })
+    });
 
     private handleGreedy = action((keyname: string) => {
         switch (keyname) {
@@ -149,7 +149,7 @@ export class KeyManager {
             stopPropagation: stopPropagation,
             preventDefault: preventDefault
         };
-    })
+    });
 
     private alt = action((keyname: string) => {
         const stopPropagation = true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 8e01d9357..9dcfde7f9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -26,7 +26,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         this._anchorDisposer?.();
     }
     @action
-    timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)))
+    timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)));
     componentDidMount() {
         this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
             action(() => {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 15e35ce72..a998386d8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -60,6 +60,7 @@ import React = require("react");
 import { DocumentManager } from '../../../util/DocumentManager';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
 import { CollectionViewType } from '../../collections/CollectionView';
+import { SnappingManager } from '../../../util/SnappingManager';
 
 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
@@ -1596,7 +1597,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const active = this.active();
         const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground;
+        const interactive = (Doc.GetSelectedTool() === InkTool.None || SnappingManager.GetIsDragging()) && !this.layoutDoc._isBackground;
         selected && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props)); // need to make sure that we update a text box that is selected after updating the one that was deselected
         if (!selected && FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); }
         const minimal = this.props.ignoreAutoHeight;
@@ -1646,13 +1647,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
-                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active ? "none" : undefined }}
+                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined }}
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`,
-                                pointerEvents: !active ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
+                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
-- 
cgit v1.2.3-70-g09d2

