From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 8 Jul 2022 00:17:26 -0400
Subject: restructured currentUserUtils to avoid having import cycles.

---
 src/client/views/DocumentButtonBar.tsx | 449 ++++++++++++++++++++-------------
 1 file changed, 268 insertions(+), 181 deletions(-)

(limited to 'src/client/views/DocumentButtonBar.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9b8f7238d..bac51a11d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,57 +1,54 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+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, DocCastAsync } from "../../fields/Doc";
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
+import { Cast, NumCast } from '../../fields/Types';
+import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { Docs } from '../documents/Documents';
-import { DocumentType } from '../documents/DocumentTypes';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DragManager } from '../util/DragManager';
 import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
+import { undoBatch } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { TabDocView } from './collections/TabDocView';
 import './DocumentButtonBar.scss';
+import { Colors } from './global/globalEnums';
 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 React = require("react");
-import { PresBox } from './nodes/trails/PresBox';
-import { undoBatch } from '../util/UndoManager';
-import { CollectionViewType } from './collections/CollectionView';
-import { Colors } from './global/globalEnums';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
-const higflyout = require("@hig/flyout");
+import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
+import { TemplateMenu } from './TemplateMenu';
+import React = require('react');
+const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-const cloud: IconProp = "cloud-upload-alt";
-const fetch: IconProp = "sync-alt";
+const cloud: IconProp = 'cloud-upload-alt';
+const fetch: IconProp = 'sync-alt';
 
 enum UtilityButtonState {
     Default,
     OpenRight,
-    OpenExternally
+    OpenExternally,
 }
 
 @observer
-export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> {
+export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[]; stack?: any }, {}> {
     private _dragRef = React.createRef<HTMLDivElement>();
     private _pullAnimating = false;
     private _pushAnimating = false;
     private _pullColorAnimating = false;
 
-    @observable private pushIcon: IconProp = "arrow-alt-circle-up";
-    @observable private pullIcon: IconProp = "arrow-alt-circle-down";
-    @observable private pullColor: string = "white";
+    @observable private pushIcon: IconProp = 'arrow-alt-circle-up';
+    @observable private pullIcon: IconProp = 'arrow-alt-circle-down';
+    @observable private pullColor: string = 'white';
     @observable public isAnimatingFetch = false;
     @observable public isAnimatingPulse = false;
 
@@ -63,17 +60,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     constructor(props: { views: () => (DocumentView | undefined)[] }) {
         super(props);
-        runInAction(() => DocumentButtonBar.Instance = this);
+        runInAction(() => (DocumentButtonBar.Instance = this));
     }
 
     public startPullOutcome = action((success: boolean) => {
         if (!this._pullAnimating) {
             this._pullAnimating = true;
-            this.pullIcon = success ? "check-circle" : "stop-circle";
-            setTimeout(() => runInAction(() => {
-                this.pullIcon = "arrow-alt-circle-down";
-                this._pullAnimating = false;
-            }), 1000);
+            this.pullIcon = success ? 'check-circle' : 'stop-circle';
+            setTimeout(
+                () =>
+                    runInAction(() => {
+                        this.pullIcon = 'arrow-alt-circle-down';
+                        this._pullAnimating = false;
+                    }),
+                1000
+            );
         }
     });
 
@@ -81,11 +82,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         this.isAnimatingPulse = false;
         if (!this._pushAnimating) {
             this._pushAnimating = true;
-            this.pushIcon = success ? "check-circle" : "stop-circle";
-            setTimeout(() => runInAction(() => {
-                this.pushIcon = "arrow-alt-circle-up";
-                this._pushAnimating = false;
-            }), 1000);
+            this.pushIcon = success ? 'check-circle' : 'stop-circle';
+            setTimeout(
+                () =>
+                    runInAction(() => {
+                        this.pushIcon = 'arrow-alt-circle-up';
+                        this._pushAnimating = false;
+                    }),
+                1000
+            );
         }
     });
 
@@ -93,165 +98,241 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         this.isAnimatingFetch = false;
         if (!this._pullColorAnimating) {
             this._pullColorAnimating = true;
-            this.pullColor = unchanged ? "lawngreen" : "red";
+            this.pullColor = unchanged ? 'lawngreen' : 'red';
             setTimeout(this.clearPullColor, 1000);
         }
     });
 
     private clearPullColor = action(() => {
-        this.pullColor = "white";
+        this.pullColor = 'white';
         this._pullColorAnimating = false;
     });
 
-    get view0() { return this.props.views()?.[0]; }
+    get view0() {
+        return this.props.views()?.[0];
+    }
 
     @computed
     get considerGoogleDocsPush() {
         const targetDoc = this.view0?.props.Document;
         const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
-        const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
-        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
-            <div
-                className="documentButtonBar-button"
-                style={{ animation }}
-                onClick={async () => {
-                    await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
-                    !published && runInAction(() => this.isAnimatingPulse = true);
-                    DocumentButtonBar.hasPushedHack = false;
-                    targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
-                }}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
-            </div></Tooltip>;
+        const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
+        return !targetDoc ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">{`${published ? 'Push' : 'Publish'} to Google Docs`}</div>
+                    </>
+                }>
+                <div
+                    className="documentButtonBar-button"
+                    style={{ animation }}
+                    onClick={async () => {
+                        await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+                        !published && runInAction(() => (this.isAnimatingPulse = true));
+                        DocumentButtonBar.hasPushedHack = false;
+                        targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+                    }}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? 'sm' : 'xs'} />
+                </div>
+            </Tooltip>
+        );
     }
 
     @computed
     get considerGoogleDocsPull() {
         const targetDoc = this.view0?.props.Document;
         const dataDoc = targetDoc && Doc.GetProto(targetDoc);
-        const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+        const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
 
         const title = (() => {
             switch (this.openHover) {
                 default:
-                case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`;
-                case UtilityButtonState.OpenRight: return "Open in Right Split";
-                case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+                case UtilityButtonState.Default:
+                    return `${!dataDoc?.googleDocUnchanged ? 'Pull from' : 'Fetch'} Google Docs`;
+                case UtilityButtonState.OpenRight:
+                    return 'Open in Right Split';
+                case UtilityButtonState.OpenExternally:
+                    return 'Open in new Browser Tab';
             }
         })();
 
-        return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
-            title={<><div className="dash-tooltip">{title}</div></>}>
-            <div className="documentButtonBar-button"
-                style={{ backgroundColor: this.pullColor }}
-                onPointerEnter={action(e => {
-                    if (e.altKey) {
-                        this.openHover = UtilityButtonState.OpenExternally;
-                    } else if (e.shiftKey) {
-                        this.openHover = UtilityButtonState.OpenRight;
-                    }
-                })}
-                onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
-                onClick={async e => {
-                    const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
-                    if (e.shiftKey) {
-                        e.preventDefault();
-                        let googleDoc = await Cast(dataDoc.googleDoc, Doc);
-                        if (!googleDoc) {
-                            const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false };
-                            googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
-                            dataDoc.googleDoc = googleDoc;
+        return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">{title}</div>
+                    </>
+                }>
+                <div
+                    className="documentButtonBar-button"
+                    style={{ backgroundColor: this.pullColor }}
+                    onPointerEnter={action(e => {
+                        if (e.altKey) {
+                            this.openHover = UtilityButtonState.OpenExternally;
+                        } else if (e.shiftKey) {
+                            this.openHover = UtilityButtonState.OpenRight;
                         }
-                        CollectionDockingView.AddSplit(googleDoc, "right");
-                    } else if (e.altKey) {
-                        e.preventDefault();
-                        window.open(googleDocUrl);
-                    } else {
-                        this.clearPullColor();
-                        DocumentButtonBar.hasPulledHack = false;
-                        targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
-                        dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true);
-                    }
-                }}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm"
-                    style={{ WebkitAnimation: animation, MozAnimation: animation }}
-                    icon={(() => {
-                        switch (this.openHover) {
-                            default:
-                            case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
-                            case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
-                            case UtilityButtonState.OpenExternally: return "share";
+                    })}
+                    onPointerLeave={action(() => (this.openHover = UtilityButtonState.Default))}
+                    onClick={async e => {
+                        const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+                        if (e.shiftKey) {
+                            e.preventDefault();
+                            let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+                            if (!googleDoc) {
+                                const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false };
+                                googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+                                dataDoc.googleDoc = googleDoc;
+                            }
+                            CollectionDockingView.AddSplit(googleDoc, 'right');
+                        } else if (e.altKey) {
+                            e.preventDefault();
+                            window.open(googleDocUrl);
+                        } else {
+                            this.clearPullColor();
+                            DocumentButtonBar.hasPulledHack = false;
+                            targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+                            dataDoc.googleDocUnchanged && runInAction(() => (this.isAnimatingFetch = true));
                         }
-                    })()}
-                />
-            </div></Tooltip>;
+                    }}>
+                    <FontAwesomeIcon
+                        className="documentdecorations-icon"
+                        size="sm"
+                        style={{ WebkitAnimation: animation, MozAnimation: animation }}
+                        icon={(() => {
+                            switch (this.openHover) {
+                                default:
+                                case UtilityButtonState.Default:
+                                    return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
+                                case UtilityButtonState.OpenRight:
+                                    return 'arrow-alt-circle-right';
+                                case UtilityButtonState.OpenExternally:
+                                    return 'share';
+                            }
+                        })()}
+                    />
+                </div>
+            </Tooltip>
+        );
     }
     @computed
     get followLinkButton() {
         const targetDoc = this.view0?.props.Document;
-        return !targetDoc ? (null) : <Tooltip title={
-            <div className="dash-tooltip">{"Set onClick to follow primary link"}</div>}>
-            <div className="documentButtonBar-icon"
-                style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
-                onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
-            </div>
-        </Tooltip>;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-tooltip">{'Set onClick to follow primary link'}</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+                    onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
+                </div>
+            </Tooltip>
+        );
     }
     @computed
     get pinButton() {
         const targetDoc = this.view0?.props.Document;
-        return !targetDoc ? (null) : <Tooltip title={
-            <div className="dash-tooltip">{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}</div>}>
-            <div className="documentButtonBar-icon"
-                style={{ color: "white" }}
-                onClick={(e => TabDocView.PinDoc(this.props.views().filter(v => v).map(dv => dv!.rootDoc), {pinDocView: true}))}
-             >
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
-            </div>
-        </Tooltip>;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-tooltip">{SelectionManager.Views().length > 1 ? 'Pin multiple documents to presentation' : 'Pin to presentation'}</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onClick={e =>
+                        TabDocView.PinDoc(
+                            this.props
+                                .views()
+                                .filter(v => v)
+                                .map(dv => dv!.rootDoc),
+                            { pinDocView: true }
+                        )
+                    }>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
+                </div>
+            </Tooltip>
+        );
     }
 
     @computed
     get shareButton() {
         const targetDoc = this.view0?.props.Document;
-        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}>
-            <div className="documentButtonBar-icon" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
-            </div></Tooltip >;
+        return !targetDoc ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">{'Open Sharing Manager'}</div>
+                    </>
+                }>
+                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
+                </div>
+            </Tooltip>
+        );
     }
 
     @computed
     get menuButton() {
         const targetDoc = this.view0?.props.Document;
-        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`Open Context Menu`}</div></>}>
-            <div className="documentButtonBar-icon" style={{ color: "white", cursor: "pointer" }} onClick={e => this.openContextMenu(e)}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
-            </div></Tooltip >;
+        return !targetDoc ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">{`Open Context Menu`}</div>
+                    </>
+                }>
+                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
+                </div>
+            </Tooltip>
+        );
     }
 
     @computed
     get moreButton() {
         const targetDoc = this.view0?.props.Document;
-        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}</div></>}>
-            <div className="documentButtonBar-icon" style={{ color: "white", cursor: "e-resize" }} onClick={action(e =>
-                CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h"
-                />
-            </div></Tooltip >;
+        return !targetDoc ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">{`${SettingsManager.propertiesWidth > 0 ? 'Close' : 'Open'} Properties Panel`}</div>
+                    </>
+                }>
+                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'e-resize' }} onClick={action(e => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250))}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h" />
+                </div>
+            </Tooltip>
+        );
     }
 
     @computed
     get metadataButton() {
         const view0 = this.view0;
-        return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
-            <div className="documentButtonBar-linkFlyout">
-                <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                    content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
-                    <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
-                        {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
-                    </div>
-                </Flyout>
-            </div></Tooltip>;
+        return !view0 ? null : (
+            <Tooltip
+                title={
+                    <>
+                        <div className="dash-tooltip">Show metadata panel</div>
+                    </>
+                }>
+                <div className="documentButtonBar-linkFlyout">
+                    <Flyout
+                        anchorPoint={anchorPoints.LEFT_TOP}
+                        content={
+                            <MetadataEntryMenu
+                                docs={this.props
+                                    .views()
+                                    .filter(dv => dv)
+                                    .map(dv => dv!.props.Document)}
+                                suggestWithFunction
+                            /> /* tfs: @bcz This might need to be the data document? */
+                        }>
+                        <div className={'documentButtonBar-linkButton-' + 'empty'} onPointerDown={e => e.stopPropagation()}>
+                            {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
+                        </div>
+                    </Flyout>
+                </div>
+            </Tooltip>
+        );
     }
     @observable _aliasDown = false;
     onAliasButtonDown = action((e: React.PointerEvent): void => {
@@ -264,13 +345,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
             const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            dragData.defaultDropAction = "alias";
+            dragData.defaultDropAction = 'alias';
             dragData.canEmbed = true;
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
             return true;
         }
         return false;
-    }
+    };
 
     _ref = React.createRef<HTMLDivElement>();
     @observable _tooltipOpen: boolean = false;
@@ -278,49 +359,60 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get templateButton() {
         const view0 = this.view0;
         const views = this.props.views();
-        return !view0 ? (null) :
-            <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom">
-                <div className="documentButtonBar-linkFlyout" ref={this._dragRef}
-                    onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} >
-
-                    <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
-                        content={!this._aliasDown ? (null) :
-                            <div ref={this._ref}> <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} /></div>}>
-                        <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+        return !view0 ? null : (
+            <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
+                <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
+                    <Flyout
+                        anchorPoint={anchorPoints.LEFT_TOP}
+                        onOpen={action(() => (this._aliasDown = true))}
+                        onClose={action(() => (this._aliasDown = false))}
+                        content={
+                            !this._aliasDown ? null : (
+                                <div ref={this._ref}>
+                                    {' '}
+                                    <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} />
+                                </div>
+                            )
+                        }>
+                        <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onAliasButtonDown}>
                             {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
                         </div>
                     </Flyout>
                 </div>
-            </Tooltip>;
+            </Tooltip>
+        );
     }
 
     openContextMenu = (e: React.MouseEvent) => {
         let child = SelectionManager.Views()[0].ContentDiv!.children[0];
         while (child.children.length) {
-            const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string");
+            const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
             if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
             if (next?.className?.toString().includes(DashFieldView.name)) break;
             if (next) child = next;
             else break;
         }
         simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30);
-    }
+    };
 
     render() {
-        if (!this.view0) return (null);
+        if (!this.view0) return null;
 
         const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
         const doc = this.view0?.props.Document;
         const considerPull = isText && this.considerGoogleDocsPull;
         const considerPush = isText && this.considerGoogleDocsPush;
-        return <div className="documentButtonBar">
-            <div className="documentButtonBar-button">
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
-            </div>
-            {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
-            </div> : (null)}
-            {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
+        return (
+            <div className="documentButtonBar">
+                <div className="documentButtonBar-button">
+                    <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+                </div>
+                {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? (
+                    <div className="documentButtonBar-button">
+                        <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+                    </div>
+                ) : null}
+                {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
                 {this.templateButton}
             </div>
             /*<div className="documentButtonBar-button">
@@ -329,27 +421,22 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 {this.contextButton}
             </div> */}
-            {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) : <div className="documentButtonBar-button">
-                {this.followLinkButton}
-            </div>}
-            <div className="documentButtonBar-button">
-                {this.pinButton}
-            </div>
-            {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
-                {this.shareButton}
-            </div>}
-            {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
-                {this.considerGoogleDocsPush}
-            </div>}
-            <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
-                {this.considerGoogleDocsPull}
-            </div>
-            <div className="documentButtonBar-button">
-                {this.menuButton}
-            </div>
-            {/* {Doc.noviceMode ? (null) : <div className="documentButtonBar-button">
+                {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+                <div className="documentButtonBar-button">{this.pinButton}</div>
+                {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
+                {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
+                    <div className="documentButtonBar-button" style={{ display: !considerPush ? 'none' : '' }}>
+                        {this.considerGoogleDocsPush}
+                    </div>
+                )}
+                <div className="documentButtonBar-button" style={{ display: !considerPull ? 'none' : '' }}>
+                    {this.considerGoogleDocsPull}
+                </div>
+                <div className="documentButtonBar-button">{this.menuButton}</div>
+                {/* {Doc.noviceMode ? (null) : <div className="documentButtonBar-button">
                 {this.moreButton}
             </div>} */}
-        </div>;
+            </div>
+        );
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/DocumentButtonBar.tsx')

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

