From bc19fd4221c1bd06135c894e5ed2edcfdb61b0be Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Jul 2023 15:09:35 -0400
Subject: fixed FontIconBox scripts to set 'this' to fix tool bars buttons not
 working. enabled access to contextMenu in treeView and topBar even when
 hideContextMenu is set

---
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 165 +++++++++------------
 1 file changed, 74 insertions(+), 91 deletions(-)

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

diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 3ec53beac..da1b89200 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -86,10 +86,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     }
     Icon = (color: string, iconFalse?: boolean) => {
         let icon;
-        if (iconFalse ) {
+        if (iconFalse) {
             icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any;
-            if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />
-            else return null
+            if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
+            else return null;
         }
         icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
         const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
@@ -124,36 +124,38 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      */
     @computed get numberDropdown() {
         let type: NumberDropdownType;
-        switch(this.type) {
-            case ButtonType.NumberDropdownButton: 
-                type = 'dropdown'
+        switch (this.type) {
+            case ButtonType.NumberDropdownButton:
+                type = 'dropdown';
                 break;
             case ButtonType.NumberInlineButton:
-                type = 'input'
+                type = 'input';
                 break;
             case ButtonType.NumberSliderButton:
             default:
-                type = 'slider'
+                type = 'slider';
                 break;
         }
-        const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
+        const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined });
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         // Script for checking the outcome of the toggle
         const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
         const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
 
-        return <NumberDropdown 
-            color={color} 
-            numberDropdownType={type} 
-            showPlusMinus={false} 
-            tooltip={this.label}
-            type={Type.PRIM}
-            min={NumCast(this.rootDoc.numBtnMin, 0)}
-            max={NumCast(this.rootDoc.numBtnMax, 100)}
-            number={checkResult} 
-            setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}
-            fillWidth
-        />
+        return (
+            <NumberDropdown
+                color={color}
+                numberDropdownType={type}
+                showPlusMinus={false}
+                tooltip={this.label}
+                type={Type.PRIM}
+                min={NumCast(this.rootDoc.numBtnMin, 0)}
+                max={NumCast(this.rootDoc.numBtnMax, 100)}
+                number={checkResult}
+                setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}
+                fillWidth
+            />
+        );
     }
 
     /**
@@ -202,84 +204,69 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         let dropdown = true;
         let getStyle: (val: string) => any = () => {};
         let icon: IconProp = 'caret-down';
-        let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView')
+        let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView');
         try {
             if (isViewDropdown) {
                 const selectedDocs: Doc[] = SelectionManager.Docs();
                 const selected = SelectionManager.Docs().lastElement();
-                console.log('selected')
+                console.log('selected');
                 if (selected) {
                     if (StrCast(selected.type) === DocumentType.COL) {
                         text = StrCast(selected._type_collection);
-                        console.log("collection selected", text)
+                        console.log('collection selected', text);
                     } else {
-                        console.log("doc selected", selected.title);
+                        console.log('doc selected', selected.title);
                         dropdown = false;
                         if (selectedDocs.length > 1) {
-                            text = selectedDocs.length + " selected"
+                            text = selectedDocs.length + ' selected';
                         } else {
                             text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType);
                             icon = Doc.toIcon(selected);
                         }
-                        return <Popup
-                            icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
-                            text={text}
-                            type={Type.TERT}
-                            color={color}
-                            popup={<SelectedDocView selectedDocs={selectedDocs}/>}
-                            fillWidth
-                        />
+                        return <Popup icon={<FontAwesomeIcon size={'1x'} icon={icon} />} text={text} type={Type.TERT} color={color} popup={<SelectedDocView selectedDocs={selectedDocs} />} fillWidth />;
                     }
                 } else {
                     dropdown = false;
-                    return <Button 
-                        text={`None Selected`} 
-                        type={Type.TERT}
-                        color={color}
-                        fillWidth
-                        inactive
-                    />
+                    return <Button text={`None Selected`} type={Type.TERT} color={color} fillWidth inactive />;
                 }
                 noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
-            } else { 
+            } else {
                 text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-                getStyle = (val: string) => { return { fontFamily: val } }
+                getStyle = (val: string) => {
+                    return { fontFamily: val };
+                };
             }
         } catch (e) {
             console.log(e);
         }
 
-        console.log("current item: ", text);
+        console.log('current item: ', text);
 
         // Get items to place into the list
         const list: IListItemProps[] = this.buttonList
             .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
-            .map(value => (
-                {
-                    text: value.charAt(0).toUpperCase() + value.slice(1),
-                    val: value,
-                    style: getStyle(value),
-                    onClick: undoable(() => script.script.run({ self: this.rootDoc, value }), value)
-                    // shortcut: '#',
-                }
-            ));
-
+            .map(value => ({
+                text: value.charAt(0).toUpperCase() + value.slice(1),
+                val: value,
+                style: getStyle(value),
+                onClick: undoable(() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), value),
+                // shortcut: '#',
+            }));
 
         return (
-            <Dropdown 
+            <Dropdown
                 selectedVal={text}
-                setSelectedVal={undoable((val) => script.script.run({ self: this.rootDoc, val }), `dropdown select ${this.label}`)}
-                color={color} 
-                type={isViewDropdown ? Type.TERT : Type.PRIM} 
-                dropdownType={DropdownType.SELECT} 
+                setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
+                color={color}
+                type={isViewDropdown ? Type.TERT : Type.PRIM}
+                dropdownType={DropdownType.SELECT}
                 items={list}
                 tooltip={this.label}
                 fillWidth
             />
-        )
+        );
     }
 
-
     @computed get colorScript() {
         return ScriptCast(this.rootDoc.script);
     }
@@ -290,14 +277,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     @computed get colorButton() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
+        const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
         const tooltip: string = StrCast(this.rootDoc.toolTip);
 
         return (
-            <ColorPicker 
-                setSelectedColor={(value) => {
+            <ColorPicker
+                setSelectedColor={value => {
                     const s = this.colorScript;
-                    s && undoable(() => s.script.run({ self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)();
+                    s && undoable(() => s.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)();
                 }}
                 selectedColor={curColor}
                 type={Type.PRIM}
@@ -306,7 +293,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 tooltip={tooltip}
                 label={this.label}
             />
-        )
+        );
     }
 
     @computed get toggleButton() {
@@ -315,25 +302,24 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const tooltip: string = StrCast(this.rootDoc.toolTip);
 
         const script = ScriptCast(this.rootDoc.onClick);
-        const toggleStatus = script ? script.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
         // Colors
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
 
-        console.log(tooltip, toggleStatus);
         return (
-            <Toggle 
-                tooltip={`Toggle ${tooltip}`} 
-                toggleType={ToggleType.BUTTON} 
-                type={Type.PRIM} 
-                toggleStatus={toggleStatus} 
-                text={buttonText} 
-                color={color} 
-                icon={this.Icon(color)!} 
+            <Toggle
+                tooltip={`Toggle ${tooltip}`}
+                toggleType={ToggleType.BUTTON}
+                type={Type.PRIM}
+                toggleStatus={toggleStatus}
+                text={buttonText}
+                color={color}
+                icon={this.Icon(color)!}
                 label={this.label}
-                onPointerDown={() => script.script.run({ self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
+                onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
             />
-        )
+        );
     }
 
     /**
@@ -344,28 +330,24 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const tooltip: string = StrCast(this.rootDoc.toolTip);
 
-        return (
-            <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label}/>
-        )
+        return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
     }
 
     @computed get editableText() {
         // Script for running the toggle
         const script = ScriptCast(this.rootDoc.script);
         // Function to run the script
-        const checkResult = script?.script.run({ value: '', _readOnly_: true }).result;
+        const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+
+        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).result;
+
+        return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
 
-        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result;
-        
-        return <EditableText 
-            editing={false} setEditing={(editing: boolean) => {}}            
-        />
-        
         return (
             <div className="menuButton editableText">
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
                 <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
-                    <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+                    <EditableView GetValue={() => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
                 </div>
             </div>
         );
@@ -403,7 +385,8 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             case ButtonType.ClickButton:
             case ButtonType.ToolButton: 
                 button = (
-                    <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ _readOnly_: false })} color={color} icon={this.Icon(color)!} label={this.label}/>
+                    <IconButton tooltip={tooltip} 
+                    color={color} icon={this.Icon(color)!} label={this.label}/>
                 );
                 break;
             case ButtonType.TextButton: 
@@ -412,7 +395,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 );
                 break;
             case ButtonType.MenuButton: button = (
-                    <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>
+                    <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>
                 );
                 break;
         }
-- 
cgit v1.2.3-70-g09d2


From 08f125f5880247280c02633feeb31a8df1912b97 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 31 Jul 2023 00:06:16 -0400
Subject: fixed dictation into text boxes to stop when component unmounts.  
 fixed some icons.  fixed multiToggle for alignments.  added link docs to user
 cache.  fixed background color for tab stack buttons.    added a bunch of
 @computeds to try to help performacne with lots of docs.  chnaged text boxes
 to no expand/contract padding when selected.

---
 package-lock.json                                  | 103 ++-------------------
 package.json                                       |   2 +-
 src/client/DocServer.ts                            |   3 +-
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |  58 ++++++------
 src/client/util/RTFMarkup.tsx                      |   2 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 .../views/collections/CollectionDockingView.scss   |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  20 ++--
 .../collectionLinear/CollectionLinearView.tsx      |  33 +++----
 .../collectionSchema/CollectionSchemaView.tsx      |   1 -
 src/client/views/global/globalScripts.ts           |  52 +++++------
 src/client/views/nodes/DocumentView.tsx            |  55 ++++++-----
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  72 +++++++-------
 .../nodes/formattedText/FormattedTextBox.scss      |  20 +---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  48 ++++++----
 src/fields/Doc.ts                                  |   2 +-
 src/fields/List.ts                                 |  46 ++++-----
 18 files changed, 228 insertions(+), 298 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 54d1b568c..082531ea0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6351,9 +6351,9 @@
             }
         },
         "browndash-components": {
-            "version": "0.0.90",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.90.tgz",
-            "integrity": "sha512-56dsp2yoP5axTnXSPtSpWsQUkcj3hAekYBHLk7Kw6OV7LHVuu5PZruSmGE9xDrdhAFz2UdUyxKeAGB3PdbQoew==",
+            "version": "0.0.91",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.91.tgz",
+            "integrity": "sha512-VYW1C1XY6CcQD4OceQHK/2VGhSa0H0iboom7M9zy5F6WHJP03LFHrwKkRtgsqwxQBQLsj69XXlEfygbSkV3FvQ==",
             "requires": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
@@ -6902,97 +6902,12 @@
                                         "strip-ansi": "^7.0.1"
                                     }
                                 },
-                                "string-width-cjs": {
-                                    "version": "npm:string-width@4.2.3",
-                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                    "requires": {
-                                        "emoji-regex": "^8.0.0",
-                                        "is-fullwidth-code-point": "^3.0.0",
-                                        "strip-ansi": "^6.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
-                                },
                                 "strip-ansi": {
                                     "version": "7.1.0",
                                     "bundled": true,
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
-                                },
-                                "strip-ansi-cjs": {
-                                    "version": "npm:strip-ansi@6.0.1",
-                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                    "requires": {
-                                        "ansi-regex": "^5.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        }
-                                    }
-                                },
-                                "wrap-ansi-cjs": {
-                                    "version": "npm:wrap-ansi@7.0.0",
-                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-                                    "requires": {
-                                        "ansi-styles": "^4.0.0",
-                                        "string-width": "^4.1.0",
-                                        "strip-ansi": "^6.0.0"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "string-width": {
-                                            "version": "4.2.3",
-                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                            "requires": {
-                                                "emoji-regex": "^8.0.0",
-                                                "is-fullwidth-code-point": "^3.0.0",
-                                                "strip-ansi": "^6.0.1"
-                                            }
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
                                 }
                             }
                         },
@@ -8497,7 +8412,7 @@
                             }
                         },
                         "string-width-cjs": {
-                            "version": "npm:string-width-cjs@4.2.3",
+                            "version": "npm:string-width@4.2.3",
                             "bundled": true,
                             "requires": {
                                 "emoji-regex": "^8.0.0",
@@ -8520,7 +8435,7 @@
                             }
                         },
                         "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi-cjs@6.0.1",
+                            "version": "npm:strip-ansi@6.0.1",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^5.0.1"
@@ -8679,7 +8594,7 @@
                             }
                         },
                         "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi-cjs@7.0.0",
+                            "version": "npm:wrap-ansi@7.0.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-styles": "^4.0.0",
@@ -9865,9 +9780,9 @@
             "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw=="
         },
         "core-js-compat": {
-            "version": "3.31.1",
-            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz",
-            "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==",
+            "version": "3.32.0",
+            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz",
+            "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==",
             "requires": {
                 "browserslist": "^4.21.9"
             }
diff --git a/package.json b/package.json
index 8da591254..822460a10 100644
--- a/package.json
+++ b/package.json
@@ -177,7 +177,7 @@
         "body-parser": "^1.19.2",
         "bootstrap": "^4.6.1",
         "brotli": "^1.3.3",
-        "browndash-components": "^0.0.90",
+        "browndash-components": "^0.0.91",
         "browser-assert": "^1.2.1",
         "bson": "^4.6.1",
         "canvas": "^2.9.3",
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ba59a9f50..ad5a73598 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -49,7 +49,8 @@ export namespace DocServer {
         }
         const filtered = Array.from(Object.keys(_cache)).filter(key => {
             const doc = _cache[key] as Doc;
-            if (!(StrCast(doc.author).includes('.edu') || StrCast(doc.author).includes('.com')) || doc.author == Doc.CurrentUserEmail) return true;
+            return true;
+            if (!(StrCast(doc.author).includes('.edu') || StrCast(doc.author).includes('.com')) || doc.author === Doc.CurrentUserEmail) return true;
             return false;
         });
         if (filtered.length === lastCacheUpdate) return;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f22fa9f17..d38e2292c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -636,7 +636,7 @@ export namespace Docs {
                 DocumentType.CONFIG,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { layout_hideLinkButton: true, pointerEvents: 'none', layout_unrendered: true },
+                    options: { layout_hideLinkButton: true, layout_unrendered: true },
                 },
             ],
             [
@@ -854,7 +854,7 @@ export namespace Docs {
             dataProps.author_date = new DateField();
             if (fieldKey) {
                 dataProps[`${fieldKey}_modificationDate`] = new DateField();
-                dataProps[fieldKey] = data;
+                dataProps[fieldKey] = options.data ?? data;
 
                 // so that the list of annotations is already initialised, prevents issues in addonly.
                 // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a86011042..8e91bf83f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -586,6 +586,11 @@ export class CurrentUserUtils {
         dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
         _lockedPosition: true, isSystem: true, flexDirection: "row"
     })
+    static multiToggleList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.FontIconDocument({
+        ...opts, data:docs, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
+        dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
+        _lockedPosition: true, isSystem: true, flexDirection: "row"
+    })
 
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
         btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, 
@@ -640,17 +645,17 @@ export class CurrentUserUtils {
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
             { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip:"Font highlight",        btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Alignment",toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                  toolType:"alignment", ignoreClick: true,
+            { title: "Align",    toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
-                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
-                { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-                { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+                { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
               ]
             },
             { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
@@ -730,32 +735,31 @@ export class CurrentUserUtils {
         return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
     }
 
+    static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
+        const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
+        if (!params.subMenu) { // button does not have a sub menu
+            return this.setupContextMenuButton(params, menuBtnDoc);
+        }  
+        // linear view
+        const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
+            childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+            linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton};
+        
+        const items = !menuBtnDoc ? [] : params.subMenu?.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+        if (params.btnType === ButtonType.MultiToggleButton) {
+            const list = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), 
+                (opts) => this.multiToggleList(opts, items??[]), reqdSubMenuOpts, items), params.scripts);
+            return list;
+        }
+        return DocUtils.AssignScripts(
+            DocUtils.AssignDocField(menuDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), params.scripts, params.funcs);
+    }
+
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
         const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
-        const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
-            const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
-            if (!params.subMenu) { // button does not have a sub menu
-                return this.setupContextMenuButton(params, menuBtnDoc);
-            } else { // linear view
-                let reqdSubMenuOpts;
-                if (params.btnType === ButtonType.MultiToggleButton) {
-                    reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-                    childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-                    linearView_SubMenu: true, linearView_Dropdown: true, };
-                } else {
-                    reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-                    childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-                    linearView_SubMenu: true, linearView_Expandable: true, };
-                }
-                const items = params.subMenu?.map(sub =>
-                    this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
-                );
-                return DocUtils.AssignScripts(
-                    DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), params.scripts, params.funcs);
-            }
-        });
+        const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
         return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
     }
 
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index b93d4f293..c2f121e1f 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -30,7 +30,7 @@ export class RTFMarkup extends React.Component<{}> {
      */
     @computed get cheatSheet() {
         return (
-            <div style={{ textAlign: 'initial', height: '100%' }}>
+            <div style={{ background: 'white', textAlign: 'initial', height: '100%' }}>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 2e3668268..b74eabcc3 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -244,10 +244,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @computed get layout_autoHeightButton() {
         // store previous dimensions to store old values
         return this.propertyToggleBtn(
-            on => 'Auto\xA0Size',
+            on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
             '_layout_autoHeight',
             on => `Automatical vertical sizing to show all content`,
-            on => 'arrows-alt-v'
+            on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
         );
     }
 
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index a4c5229aa..d93015506 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -176,6 +176,7 @@
         display: flex;
         align-content: center;
         justify-content: center;
+        background: $dark-gray;
     }
 
     .lm_controls > li {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba31916a7..b5e9994dd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1264,13 +1264,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    pointerEvents = () => {
+    @computed get _pointerEvents() {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         const pointerEvents = DocumentDecorations.Instance.Interacting
             ? 'none'
             : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
-    };
+    }
+    pointerEvents = () => this._pointerEvents;
+    childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
@@ -1299,7 +1301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive}
-                isContentActive={this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction}
+                isContentActive={this.childContentsActive}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this.props.addDocument}
@@ -1435,21 +1437,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }.bind(this)
     );
 
-    childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
-        return this._layoutPoolData.get(doc[Id] + (replica || ''));
-    };
+    childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || ''));
     childDataProvider = computedFn(
         function childDataProvider(this: any, doc: Doc, replica: string) {
-            return this._layoutPoolData.get(doc[Id] + (replica || ''));
+            return this.childPositionProviderUnmemoized(doc, replica);
         }.bind(this)
     );
 
-    childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
-        return this._layoutSizeData.get(doc[Id] + (replica || ''));
-    };
+    childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || ''));
     childSizeProvider = computedFn(
         function childSizeProvider(this: any, doc: Doc, replica: string) {
-            return this._layoutSizeData.get(doc[Id] + (replica || ''));
+            return this.childSizeProviderUnmemoized(doc, replica);
         }.bind(this)
     );
 
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 56b8366d0..2254b2e5f 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -204,7 +204,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
         const menuOpener = (
             <Toggle
-                text={Cast(this.props.Document.icon, 'string', null)} 
+                text={Cast(this.props.Document.icon, 'string', null)}
                 icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}
                 type={Type.TERT}
                 color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}
@@ -212,7 +212,7 @@ export class CollectionLinearView extends CollectionSubView() {
                 toggleType={ToggleType.BUTTON}
                 toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
                 onClick={() => {
-                        this.layoutDoc.linearView_IsOpen = !isExpanded;
+                    this.layoutDoc.linearView_IsOpen = !isExpanded;
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
                 fillWidth={true}
@@ -223,25 +223,22 @@ export class CollectionLinearView extends CollectionSubView() {
         return (
             <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}>
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
-                    {this.props.Document.linearView_Dropdown ? 
-                        <div>Hello World!</div>
-                        : 
+                    {
                         <>
-                        {!this.props.Document.linearView_Expandable ? null : menuOpener}
-                        {!this.layoutDoc.linearView_IsOpen ? null : (
-                            <div
-                                className="collectionLinearView-content"
-                                style={{
-                                    height: this.dimension(),
-                                    flexDirection: flexDir as any,
-                                    gap: flexGap,
-                                }}>
-                                {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
-                            </div>
-                        )}
+                            {menuOpener}
+                            {!this.layoutDoc.linearView_IsOpen ? null : (
+                                <div
+                                    className="collectionLinearView-content"
+                                    style={{
+                                        height: this.dimension(),
+                                        flexDirection: flexDir as any,
+                                        gap: flexGap,
+                                    }}>
+                                    {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+                                </div>
+                            )}
                         </>
                     }
-                    
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index ee5bf82ed..babe5c810 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -810,7 +810,6 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get sortedDocs() {
-        trace();
         const field = StrCast(this.layoutDoc.sortField);
         const desc = BoolCast(this.layoutDoc.sortDesc);
         const docs = !field
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index b906065a0..61920cdef 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,26 +1,28 @@
-import { Colors } from "browndash-components";
-import { runInAction, action } from "mobx";
-import { aggregateBounds } from "../../../Utils";
-import { Doc } from "../../../fields/Doc";
-import { Width, Height } from "../../../fields/DocSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, StrCast, NumCast, BoolCast } from "../../../fields/Types";
-import { WebField } from "../../../fields/URLField";
-import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../util/LinkManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
-import { GestureOverlay } from "../GestureOverlay";
-import { InkTranscription } from "../InkTranscription";
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from "../InkingStroke";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { WebBox } from "../nodes/WebBox";
-import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { Colors } from 'browndash-components';
+import { runInAction, action } from 'mobx';
+import { aggregateBounds } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
+import { Width, Height } from '../../../fields/DocSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
+import { GestureOverlay } from '../GestureOverlay';
+import { InkTranscription } from '../InkTranscription';
+import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { WebBox } from '../nodes/WebBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentType } from '../../documents/DocumentTypes';
 
-ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views().length <= 0; }, "are no document selected");
+ScriptingGlobals.add(function IsNoneSelected() {
+    return SelectionManager.Views().length <= 0;
+}, 'are no document selected');
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setView(view: string) {
@@ -52,7 +54,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             if (contentFrameNumber !== undefined) {
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
             } else {
-                console.log('setting color to: ', color)
+                console.log('setting color to: ', color);
                 dv.rootDoc['_' + fieldKey] = color;
             }
         });
@@ -119,7 +121,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
    ]);
 
     if (checkResult) {
-        console.log(attr, map.get(attr)?.checkResult(selected))
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
@@ -193,10 +194,9 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
-        console.log(charStyle, checkResult, map.get(charStyle)?.checkResult());
         return map.get(charStyle)?.checkResult();
     }
-    map.get(charStyle)?.toggle();
+    undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
 });
 
 export function checkInksToGroup() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2990e2159..70d2f95ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -314,7 +314,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get titleHeight() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
     }
-    get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
+    @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
         return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : ''));
     }
     @computed get finalLayoutKey() {
@@ -862,28 +862,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
     };
 
-    rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
+    @computed get _rootSelected() {
+        return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false;
+    }
+    rootSelected = (outsideReaction?: boolean) => this._rootSelected;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    isContentActive = (outsideReaction?: boolean): boolean | undefined => {
+    @computed get _isContentActive() {
         //  true  - if the document has been activated directly or indirectly (by having its children selected)
         //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
         // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
-        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' || (this.rootDoc.pointerEvents === 'none' && !StrCast(this.props.LayoutTemplateString).includes(KeyValueBox.name))
+        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
             ? false
             : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
             ? true
             : undefined;
-    };
+    }
+    isContentActive = (): boolean | undefined => this._isContentActive;
     @observable _retryThumb = 1;
-    thumbShown = () => {
-        const childHighlighted = () =>
-            Array.from(Doc.highlightedDocs.keys())
-                .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
-                .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
+    @computed get _thumbShown() {
+        const childHighlighted = () => false;
+        // Array.from(Doc.highlightedDocs.keys())
+        //     .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
+        //     .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
         const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
         return !this.props.LayoutTemplateString &&
             !this.isContentActive() &&
@@ -893,12 +897,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking)
             ? true
             : false;
-    };
+    }
+    thumbShown = () => this._thumbShown;
     childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
     /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
-    contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents);
-
+    @computed get _contentPointerEvents() {
+        return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
+    }
+    contentPointerEvents = () => this._contentPointerEvents;
     @computed get contents() {
         TraceMobx();
         const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
@@ -1081,7 +1088,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                     minWidth: 50 * ffscale(),
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
@@ -1216,16 +1223,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
         }
     }
+    @computed get highlighting() {
+        return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
+    }
+    @computed get borderPath() {
+        return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+    }
     render() {
         TraceMobx();
-        const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
-        const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+        const highlighting = this.highlighting;
+        const borderPath = this.borderPath;
         const boxShadow =
             this.props.treeViewDoc || !highlighting
                 ? this.boxShadow
                 : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
                 ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
-                : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+                : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
             outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1241,9 +1254,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
-                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
-                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
-                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)}
+                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
                 style={{
                     borderRadius: this.borderRounding,
                     pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index da1b89200..ad3532502 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -21,6 +21,7 @@ import { RichTextMenu } from '../formattedText/RichTextMenu';
 import './FontIconBox.scss';
 import { SelectedDocView } from '../../selectedDoc';
 import { Utils } from '../../../../Utils';
+import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa';
 
 export enum ButtonType {
     TextButton = 'textBtn',
@@ -209,13 +210,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             if (isViewDropdown) {
                 const selectedDocs: Doc[] = SelectionManager.Docs();
                 const selected = SelectionManager.Docs().lastElement();
-                console.log('selected');
                 if (selected) {
                     if (StrCast(selected.type) === DocumentType.COL) {
                         text = StrCast(selected._type_collection);
-                        console.log('collection selected', text);
                     } else {
-                        console.log('doc selected', selected.title);
                         dropdown = false;
                         if (selectedDocs.length > 1) {
                             text = selectedDocs.length + ' selected';
@@ -240,8 +238,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             console.log(e);
         }
 
-        console.log('current item: ', text);
-
         // Get items to place into the list
         const list: IListItemProps[] = this.buttonList
             .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
@@ -295,6 +291,34 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             />
         );
     }
+    @computed get multiToggleButton() {
+        // Determine the type of toggle button
+        const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+        const script = ScriptCast(this.rootDoc.onClick);
+        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        // Colors
+        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+        const items = DocListCast(this.rootDoc.data);
+        return (
+            <MultiToggle
+                tooltip={`Toggle ${tooltip}`}
+                type={Type.PRIM}
+                color={color}
+                label={this.label}
+                items={DocListCast(this.rootDoc.data).map(item => ({
+                    icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />,
+                    tooltip: StrCast(item.toolTip),
+                    val: StrCast(item.toolType),
+                }))}
+                selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
+                setSelectedVal={(val: string | number) => {
+                    const itemDoc = items.find(item => item.toolType === val);
+                    itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false });
+                }}
+            />
+        );
+    }
 
     @computed get toggleButton() {
         // Determine the type of toggle button
@@ -356,48 +380,34 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     render() {
         // determine dash button metadata
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const tooltip: string = StrCast(this.rootDoc.toolTip);
         const onClickScript = ScriptCast(this.rootDoc.onClick);
-        const script = ScriptCast(this.rootDoc.script);
         // TODO:glr Add label of button type
         let button: JSX.Element = this.defaultButton;
         // prettier-ignore
         switch (this.type) {
             case ButtonType.EditableText: 
-                button = this.editableText;
-                break;
+                button = this.editableText; break;
             case ButtonType.DropdownList:
-                button = this.dropdownListButton;
-                break;
+                button = this.dropdownListButton; break;
             case ButtonType.ColorButton: 
-                button = this.colorButton; 
-                break;
+                button = this.colorButton;  break;
             case ButtonType.NumberDropdownButton: 
             case ButtonType.NumberInlineButton:
             case ButtonType.NumberSliderButton: 
-                button = this.numberDropdown; 
-                break;
+                button = this.numberDropdown;  break;
             case ButtonType.DropdownButton: 
-                button = this.dropdownButton; 
-                break;
+                button = this.dropdownButton;  break;
+            case ButtonType.MultiToggleButton: 
+                button = this.multiToggleButton; break; 
             case ButtonType.ToggleButton: button = this.toggleButton; break;
             case ButtonType.ClickButton:
             case ButtonType.ToolButton: 
-                button = (
-                    <IconButton tooltip={tooltip} 
-                    color={color} icon={this.Icon(color)!} label={this.label}/>
-                );
-                break;
+                button = <IconButton tooltip={tooltip} color={color} icon={this.Icon(color)!} label={this.label}/>; break;
             case ButtonType.TextButton: 
-                button = (
-                    <Button tooltip={tooltip} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/>
-                );
-                break;
-            case ButtonType.MenuButton: button = (
-                    <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>
-                );
-                break;
+                button = <Button tooltip={tooltip} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/>; break;
+            case ButtonType.MenuButton: 
+                button = <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>; break;
         }
 
         return button;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 109b62e6f..348bdd79e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -84,7 +84,6 @@ audiotag:hover {
     height: 11;
 }
 
-.formattedTextBox-outer-selected,
 .formattedTextBox-outer {
     position: relative;
     overflow: auto;
@@ -92,9 +91,6 @@ audiotag:hover {
     width: 100%;
     height: unset;
 }
-.formattedTextBox-outer-selected {
-    cursor: text;
-}
 
 .formattedTextBox-sidebar-handle {
     position: absolute;
@@ -148,10 +144,8 @@ audiotag:hover {
 }
 
 .formattedTextBox-inner-rounded,
-.formattedTextBox-inner-rounded-selected,
 .formattedTextBox-inner,
-.formattedTextBox-inner-minimal,
-.formattedTextBox-inner-selected {
+.formattedTextBox-inner-minimal {
     height: 100%;
     white-space: pre-wrap;
     .ProseMirror:hover {
@@ -169,17 +163,6 @@ audiotag:hover {
         border-width: 1px;
     }
 }
-.formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner-selected {
-    > .ProseMirror {
-        padding: 10px;
-    }
-}
-.formattedTextBox-outer-selected {
-    > .ProseMirror:hover {
-        background: unset;
-    }
-}
 
 .gpt-typing-wrapper {
     padding: 10px;
@@ -640,7 +623,6 @@ footnote::before {
         }
     }
 
-    .formattedTextBox-outer-selected,
     .formattedTextBox-outer {
         position: relative;
         overflow: auto;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0eb328a1..24d2f0e13 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1557,7 +1557,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
     };
 
+    @action
     componentWillUnmount() {
+        if (this._recording) {
+            this._recording = !this._recording;
+        }
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
         FormattedTextBox.LiveTextUndo?.end();
@@ -1596,8 +1600,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (this._recording && !e.ctrlKey && e.button === 0) {
             this.breakupDictation();
-            e.preventDefault();
-            e.stopPropagation();
         }
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -2043,17 +2045,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     _oldWheel: any;
+    @computed get fontColor() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+    }
+    @computed get fontSize() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+    }
+    @computed get fontFamily() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+    }
+    @computed get fontWeight() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+    }
     render() {
         TraceMobx();
-        const active = this.props.isContentActive();
         const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
-        if (!active && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
-        const minimal = this.props.ignoreAutoHeight;
+        setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-        const selPad = (active && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
-        const selPaddingClass = active && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : '';
         const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
@@ -2074,19 +2084,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                               width: `${100 / scale}%`,
                               height: `${100 / scale}%`,
                           }),
-                    display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
+                    // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
                     transition: 'inherit',
                     // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
-                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-                    fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
-                    fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
-                    fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+                    color: this.fontColor,
+                    fontSize: this.fontSize,
+                    fontFamily: this.fontFamily,
+                    fontWeight: this.fontWeight,
                     ...styleFromLayoutString,
                 }}>
                 <div
                     className="formattedTextBox-cont"
                     ref={this._ref}
                     style={{
+                        cursor: this.props.isContentActive() ? 'text' : undefined,
                         overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
                         height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
                         pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
@@ -2101,7 +2112,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     onPointerDown={this.onPointerDown}
                     onDoubleClick={this.onDoubleClick}>
                     <div
-                        className={`formattedTextBox-outer${active ? '-selected' : ''}`}
+                        className={`formattedTextBox-outer`}
                         ref={this._scrollRef}
                         style={{
                             width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
@@ -2110,15 +2121,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         onScroll={this.onScroll}
                         onDrop={this.ondrop}>
                         <div
-                            className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+                            className={`formattedTextBox-inner${rounded}`}
                             ref={this.createDropTarget}
                             style={{
                                 padding: StrCast(this.layoutDoc._textBoxPadding),
-                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                // pointerEvents: !active && IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined,
+                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
+                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
                             }}
                         />
                     </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5a8a6e4b6..ad557e079 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -573,7 +573,7 @@ export namespace Doc {
 
     // compare whether documents or their protos match
     export function AreProtosEqual(doc?: Doc, other?: Doc) {
-        return doc && other && Doc.GetProto(doc) === Doc.GetProto(other);
+        return doc && other && (doc === other || Doc.GetProto(doc) === Doc.GetProto(other));
     }
 
     // Gets the data document for the document.  Note: this is mis-named -- it does not specifically
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 183d644d3..f3fcc87f7 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -72,13 +72,13 @@ class ListImpl<T extends Field> extends ObjectField {
             return res;
         },
         sort(cmpFunc: any) {
-            this[Self].__realFields(); // coerce retrieving entire array
+            this[Self].__realFields; // coerce retrieving entire array
             const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
             this[SelfProxy][FieldChanged]?.();
             return res;
         },
         splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
-            this[Self].__realFields(); // coerce retrieving entire array
+            this[Self].__realFields; // coerce retrieving entire array
             items = items.map(toObjectField);
             const list = this[Self];
             const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
@@ -125,104 +125,104 @@ class ListImpl<T extends Field> extends ObjectField {
         },
         /// Accessor methods
         concat: action(function (this: any, ...items: any[]) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.map(toRealField).concat(...items);
         }),
         includes(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().includes(valueToFind, fromIndex);
+                return this[Self].__realFields.includes(valueToFind, fromIndex);
             } else {
                 return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
             }
         },
         indexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().indexOf(valueToFind, fromIndex);
+                return this[Self].__realFields.indexOf(valueToFind, fromIndex);
             }
             return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
         },
         join(separator: any) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.map(toRealField).join(separator);
         },
         lastElement() {
-            return this[Self].__realFields().lastElement();
+            return this[Self].__realFields.lastElement();
         },
         lastIndexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
+                return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
             } else {
                 return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
             }
         },
         slice(begin: number, end: number) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
         },
 
         /// Iteration methods
         entries() {
-            return this[Self].__realFields().entries();
+            return this[Self].__realFields.entries();
         },
         every(callback: any, thisArg: any) {
-            return this[Self].__realFields().every(callback, thisArg);
+            return this[Self].__realFields.every(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         filter(callback: any, thisArg: any) {
-            return this[Self].__realFields().filter(callback, thisArg);
+            return this[Self].__realFields.filter(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         find(callback: any, thisArg: any) {
-            return this[Self].__realFields().find(callback, thisArg);
+            return this[Self].__realFields.find(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         findIndex(callback: any, thisArg: any) {
-            return this[Self].__realFields().findIndex(callback, thisArg);
+            return this[Self].__realFields.findIndex(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         forEach(callback: any, thisArg: any) {
-            return this[Self].__realFields().forEach(callback, thisArg);
+            return this[Self].__realFields.forEach(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         map(callback: any, thisArg: any) {
-            return this[Self].__realFields().map(callback, thisArg);
+            return this[Self].__realFields.map(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         reduce(callback: any, initialValue: any) {
-            return this[Self].__realFields().reduce(callback, initialValue);
+            return this[Self].__realFields.reduce(callback, initialValue);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
         },
         reduceRight(callback: any, initialValue: any) {
-            return this[Self].__realFields().reduceRight(callback, initialValue);
+            return this[Self].__realFields.reduceRight(callback, initialValue);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
         },
         some(callback: any, thisArg: any) {
-            return this[Self].__realFields().some(callback, thisArg);
+            return this[Self].__realFields.some(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         values() {
-            return this[Self].__realFields().values();
+            return this[Self].__realFields.values();
         },
         [Symbol.iterator]() {
-            return this[Self].__realFields().values();
+            return this[Self].__realFields.values();
         },
     };
     static listGetter(target: any, prop: string | symbol, receiver: any): any {
@@ -262,7 +262,7 @@ class ListImpl<T extends Field> extends ObjectField {
 
     // this requests all ProxyFields at the same time to avoid the overhead
     // of separate network requests and separate updates to the React dom.
-    private __realFields() {
+    @computed private get __realFields() {
         const unrequested = this[FieldTuples].filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
         // if we find any ProxyFields that don't have a current value, then
         // start the server request for all of them
-- 
cgit v1.2.3-70-g09d2

