From 38eb3a1b3f005b79a222f76b730e883b7e2b5253 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 14:36:37 -0400
Subject: made pinning from topbar available for all docs.

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

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

diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 698a2817e..9a74b5dba 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -312,7 +312,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     updateCapturedViewContents = (presTargetDoc: Doc, activeItem: Doc) => {
         const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
-        PresBox.pinDocView(activeItem, { pinDocContent: true, pinData: PresBox.pinDataTypes(target) }, target);
+        PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
     };
 
     @computed get recordingIsInOverlay() {
-- 
cgit v1.2.3-70-g09d2


From 8a1267faf796b2e2a30a6ba9f86879854e9ee983 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 11 Apr 2023 13:51:44 -0400
Subject: removed arrangeItems context button for collections to make it a
 funciton on drop instead of a reaction.  Converted isLinkButton to be an
 onClick script.  got rid of unused PARAMS field on templates.   fixed
 PresElementBox rendering of embedded docs.

---
 src/client/documents/Documents.ts                  |  7 +--
 src/client/util/CurrentUserUtils.ts                |  4 +-
 src/client/util/DropConverter.ts                   | 10 +---
 src/client/util/LinkFollower.ts                    |  6 ++
 src/client/views/DocumentButtonBar.tsx             |  9 +--
 src/client/views/MarqueeAnnotator.tsx              |  8 ++-
 src/client/views/PropertiesButtons.tsx             |  7 ++-
 src/client/views/StyleProvider.tsx                 |  5 +-
 src/client/views/collections/CollectionMenu.tsx    | 19 +-----
 .../views/collections/CollectionNoteTakingView.tsx |  4 +-
 .../collections/CollectionStackedTimeline.tsx      |  6 +-
 .../views/collections/CollectionStackingView.tsx   |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 56 ++++++++++--------
 src/client/views/nodes/DocumentContentsView.tsx    |  3 +-
 src/client/views/nodes/DocumentView.tsx            | 67 +++++-----------------
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  7 ++-
 .../views/nodes/formattedText/DashFieldView.tsx    |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  2 +-
 src/fields/Doc.ts                                  | 10 ++--
 src/fields/documentSchemas.ts                      |  1 -
 24 files changed, 104 insertions(+), 147 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 979b294e0..7600fcac9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -263,7 +263,6 @@ export class DocumentOptions {
     caption?: RichTextField;
     opacity?: number;
     defaultBackgroundColor?: string;
-    _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked
     _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint
     hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed
     isFolder?: boolean;
@@ -508,13 +507,13 @@ export namespace Docs {
                     layout: { view: LinkBox, dataField: defaultDataKey },
                     options: {
                         childDontRegisterViews: true,
-                        _isLinkButton: true,
+                        onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                         hideLinkAnchors: true,
                         _height: 150,
                         description: '',
                         showCaption: 'description',
                         backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
-                        _removeDropProperties: new List(['isLinkButton']),
+                        _removeDropProperties: new List(['onClick']),
                     },
                 },
             ],
@@ -1695,7 +1694,7 @@ export namespace DocUtils {
                 _width: 15,
                 _height: 15,
                 _xPadding: 0,
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
             Doc.AddDocToList(context, annotationField, pushpin);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 623aaf357..3d0db04b4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -271,7 +271,7 @@ export class CurrentUserUtils {
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true,  }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
-            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: ScriptField.MakeScript("followLink(this,altKey)", {altKey:"boolean"}) }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
@@ -622,7 +622,7 @@ export class CurrentUserUtils {
             { title: "Snap\xA0Lines",icon: "th",          toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "View\xA0All",  icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Clusters",     icon: "braille",     toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Arrange",      icon: "window",      toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Arrange",      icon: "window",      toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Reset",        icon: "check",       toolTip: "Reset View",          btnType: ButtonType.ClickButton,  expertMode: false, backgroundColor:"transparent",    scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
         ]
     }
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 7c209d1e0..2e1d6ba23 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -33,15 +33,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     let any = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            const params = StrCast(d.title)
-                .match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1]
-                .replace('()', '');
-            if (params) {
-                any = makeTemplate(d, false) || any;
-                d.PARAMS = params;
-            } else {
-                any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
-            }
+            any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
             any = makeTemplate(d, false) || any;
         }
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index df61ecece..f5aae6632 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -7,6 +7,7 @@ import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/no
 import { PresBox } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
+import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
 /*
@@ -118,3 +119,8 @@ export class LinkFollower {
         });
     }
 }
+
+ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
+    SelectionManager.DeselectAll();
+    LinkFollower.FollowLink(undefined, doc, altKey);
+});
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e83ea00cf..7e1b32398 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, DocCast, NumCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast, ScriptCast } from '../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -238,11 +238,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 </Tooltip>
             );
         };
+        const followLink = ScriptCast(targetDoc?.onClick)?.script.originalScript.includes('followLink(');
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">Set onClick to follow primary link</div>}>
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
-                    style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+                    style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
                     onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
@@ -384,7 +385,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.rootDoc);
-                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
                     <div className="documentButtonBar-pinTypes">
@@ -593,7 +594,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <LinkPopup
                             key="popup"
                             showPopup={this._showLinkPopup}
-                            linkCreated={link => (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)}
+                            linkCreated={link => (link.linkDisplay = !ScriptCast(this.props.views().lastElement()?.rootDoc.onClick)?.script.originalScript.includes('followLink('))}
                             linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
                             linkFrom={() => this.props.views().lastElement()?.rootDoc}
                         />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 30867a774..6b4f8ad85 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -14,6 +14,7 @@ import { DocumentView } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import React = require('react');
+import { ScriptField } from '../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
@@ -146,7 +147,12 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             const scale = this.props.scaling?.() || 1;
             const anno = savedAnnos[0];
             const containerOffset = this.props.containerOffset?.() || [0, 0];
-            const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title });
+            const marqueeAnno = Docs.Create.FreeformDocument([], {
+                onClick: isLinkButton ? ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }) : undefined,
+                backgroundColor: color,
+                annotationOn: this.props.rootDoc,
+                title: 'Annotation on ' + this.props.rootDoc.title,
+            });
             marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
             marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop);
             marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 11e9dd9c9..0723966e1 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -289,6 +289,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             .filter(dv => dv.docView)
             .map(dv => dv.docView!)
             .forEach(docView => {
+                const linkButton = ScriptCast(docView.props.Document.onClick)?.script.originalScript.includes('followLink(');
                 docView.noOnClick();
                 switch (onClick) {
                     case 'enterPortal':
@@ -299,11 +300,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         break;
                     case 'linkInPlace':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined;
+                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
                         break;
                     case 'linkOnRight':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.addRight : undefined;
+                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
                         break;
                 }
             });
@@ -326,7 +327,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         ];
         const list = buttonList.map(value => {
             const click = () => this.handleOptionChange(value[0]);
-            const linkButton = BoolCast(this.selectedDoc._isLinkButton);
+            const linkButton = BoolCast(ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('followLink('));
             const followLoc = this.selectedDoc._followLinkLocation;
             const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index f7b9420a7..8d67f0528 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -4,7 +4,7 @@ import { Shadows } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -279,7 +279,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (doc?.isLinkButton && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (ScriptCast(doc?.onClick)?.script.originalScript.includes('followLink(') && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any))
+                return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index c83f4e689..225743748 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -246,23 +246,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         immediate: undoBatch((source: Doc[]) => {}),
         initialize: emptyFunction,
     };
-    _openLinkInCommand = {
-        params: ['target', 'container'],
-        title: 'link follow target',
-        script: `{ if (self.container?.length) {
-            getProto(self.target).linkContainer = self.container[0];
-            getProto(self.target).isLinkButton = true;
-            getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
-            }}`,
-        immediate: undoBatch((container: Doc[]) => {
-            if (container.length) {
-                Doc.GetProto(this.target).linkContainer = container[0];
-                Doc.GetProto(this.target).isLinkButton = true;
-                Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])');
-            }
-        }),
-        initialize: emptyFunction,
-    };
     _viewCommand = {
         params: ['target'],
         title: 'bookmark view',
@@ -328,7 +311,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand];
     }
     @computed get _doc_commands() {
-        return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand];
+        return Doc.noviceMode ? undefined : [this._onClickCommand];
     }
     @computed get _tree_commands() {
         return undefined;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index cb5be990d..aec0734b4 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -215,7 +215,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
     // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
     getDisplayDoc(doc: Doc, width: () => number) {
-        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
         let dref: Opt<DocumentView>;
         const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
@@ -293,7 +293,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 642d29d2a..5891cfe78 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, NumCast, ScriptCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
@@ -406,7 +406,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 useLinkSmallAnchor: true,
                 hideLinkButton: true,
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                 annotationOn: rootDoc,
                 _timelineLabel: true,
                 borderRounding: anchorEndTime === undefined ? '100%' : undefined,
@@ -450,7 +450,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     @action
     clickAnchor = (anchorDoc: Doc, clientX: number) => {
-        if (anchorDoc.isLinkButton) {
+        if (ScriptCast(anchorDoc.onClick)?.script.originalScript.includes('followLink(')) {
             LinkFollower.FollowLink(undefined, anchorDoc, false);
         }
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1e02fc9d4..a85ee0e02 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -305,7 +305,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
     getDisplayDoc(doc: Doc, width: () => number, count: number) {
-        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
 
         let dref: Opt<DocumentView>;
@@ -323,7 +323,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 styleProvider={this.styleProvider}
                 docViewPath={this.props.docViewPath}
                 fitWidth={this.props.childFitWidth}
-                isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
+                isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
                 onBrowseClick={this.props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
@@ -381,7 +381,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1ecc69174..7ae7be3c8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -349,6 +349,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
         const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
@@ -370,6 +371,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
+        if (this.layoutDoc._autoArrange || de.metaKey) {
+            const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
+            sorted.splice(
+                sorted.findIndex(pair => pair.layout === refDoc),
+                1
+            );
+            if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
+                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
+                const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
+                const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
+
+                let lastx = NumCast(refDoc.x);
+                let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
+                setTimeout(
+                    action(() =>
+                        sorted.slice(1).forEach((pair, i) => {
+                            lastx = pair.layout.x = lastx + deltax;
+                            lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+                        })
+                    )
+                );
+            }
+        }
+
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
         return true;
     }
@@ -1409,24 +1434,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
-        if (this.layoutDoc._autoArrange) {
-            const sorted = this.childLayoutPairs.slice().sort((a, b) => (NumCast(a.layout.y) < NumCast(b.layout.y) ? -1 : 1));
-            if (sorted.length > 1) {
-                const deltay = sorted.length > 1 ? NumCast(sorted[1].layout.y) - (NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height)) : 0;
-                const deltax = sorted.length > 1 ? NumCast(sorted[1].layout.x) - NumCast(sorted[0].layout.x) : 0;
-
-                let lastx = NumCast(sorted[0].layout.x);
-                let lasty = NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
-                setTimeout(
-                    action(() =>
-                        sorted.slice(1).forEach((pair, i) => {
-                            lastx = pair.layout.x = lastx + deltax;
-                            lasty = (pair.layout.y = lasty + deltay) + NumCast(pair.layout._height);
-                        })
-                    )
-                );
-            }
-        }
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
         return [] as ViewDefResult[];
     }
@@ -1712,11 +1719,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             },
             icon: 'compress-arrows-alt',
         });
-        appearanceItems.push({
-            description: 'Toggle auto arrange',
-            event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
-            icon: 'compress-arrows-alt',
-        });
+        !Doc.noviceMode &&
+            appearanceItems.push({
+                description: 'Toggle auto arrange',
+                event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
+                icon: 'compress-arrows-alt',
+            });
         if (this.props.setContentView === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
@@ -1729,7 +1737,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
-        appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+        this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
         this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 9e56de8c2..2d12005fb 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -139,7 +139,6 @@ export class DocumentContentsView extends React.Component<
         return proto instanceof Promise ? undefined : proto;
     }
     get layoutDoc() {
-        const params = StrCast(this.props.Document.PARAMS);
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
         // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
         const template: Doc =
@@ -147,7 +146,7 @@ export class DocumentContentsView extends React.Component<
             (this.props.LayoutTemplateString && this.props.Document) ||
             (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
             Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
-        return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey);
+        return Doc.expandTemplateLayout(template, this.props.Document, this.props.layoutKey);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e24bf35f5..a39c3cf8f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -469,11 +469,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             ? this.props.select(false)
                             : '';
                     clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
-                } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                    clickFunc = () => {
-                        SelectionManager.DeselectAll();
-                        LinkFollower.FollowLink(undefined, this.Document, e.altKey);
-                    };
                 } else {
                     // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -563,48 +558,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     @action
     toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
-        this.Document.ignoreClick = false;
-        if (setTargetToggle) {
-            this.Document.followLinkToggle = !this.Document.followLinkToggle;
-            this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton;
-        } else {
-            this.Document._isLinkButton = !this.Document._isLinkButton;
-        }
-        if (this.Document._isLinkButton && !this.onClickHandler) {
-            zoom !== undefined && (this.Document.followLinkZoom = zoom);
-        } else if (this.Document._isLinkButton && this.onClickHandler) {
-            this.Document._isLinkButton = false;
-            this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
-        }
-    };
-    @undoBatch
-    @action
-    toggleTargetOnClick = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document._isLinkButton = true;
-        this.Document.followLinkToggle = true;
+        const hadOnClick = this.rootDoc.onClick;
+        this.noOnClick();
+        this.Document.onClick = hadOnClick ? undefined : ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
+        this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
     };
     @undoBatch
     @action
-    followLinkOnClick = (location: Opt<string>, zoom: boolean): void => {
+    followLinkOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document._isLinkButton = true;
+        this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
         this.Document.followLinkToggle = false;
-        this.Document.followLinkZoom = zoom;
-        this.Document.followLinkLocation = location;
-    };
-    @undoBatch
-    @action
-    selectOnClick = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document._isLinkButton = false;
-        this.Document.followLinkToggle = false;
-        this.Document.onClick = this.layoutDoc.onClick = undefined;
+        this.Document.followLinkZoom = false;
+        this.Document.followLinkLocation = undefined;
     };
     @undoBatch
     noOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document._isLinkButton = false;
+        this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
     };
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
@@ -652,8 +623,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             );
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
-        this.Document.followLinkZoom = true;
-        this.Document._isLinkButton = true;
+        this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
     };
 
     importDocument = () => {
@@ -744,29 +714,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     !zorders && cm.addItem({ description: 'ZOrder...', noexpand: true, subitems: zorderItems, icon: 'compass' });
                 }
 
-                !Doc.noviceMode && onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
+                onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
-                this.props.CollectionFreeFormDocumentView &&
-                    onClicks.push({
-                        description: (this.Document.followLinkZoom ? "Don't" : '') + ' zoom following link',
-                        event: () => (this.Document.followLinkZoom = !this.Document.followLinkZoom),
-                        icon: this.Document.ignoreClick ? 'unlock' : 'lock',
-                    });
 
                 if (!this.Document.annotationOn) {
                     const options = cm.findByDescription('Options...');
                     const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
                     !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
 
-                    onClicks.push({ description: this.Document.ignoreClick ? 'Select' : 'Do Nothing', event: () => (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' });
-                    onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
-                    onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' });
+                    onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
                     onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                 } else if (LinkManager.Links(this.Document).length) {
-                    onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' });
-                    onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' });
-                    onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' });
+                    onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
+                    onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' });
                 }
             }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f38ebba27..22ecaa299 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -202,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.y = NumCast(this.rootDoc.y);
         cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
         cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 3feb95ce9..31f1775e5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -47,7 +47,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (separation > 100) {
                 const dragData = new DragManager.DocumentDragData([this.rootDoc]);
                 dragData.dropAction = 'alias';
-                dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'isLinkButton'];
+                dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'onClick'];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
                 return true;
             } else {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e38d7e2a8..502811e35 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -119,7 +119,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         cropping.y = NumCast(this.rootDoc.y);
         cropping._width = anchw;
         cropping._height = anchh;
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e14ad4b05..83ad5628c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -33,6 +33,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
+import { ScriptField } from '../../../fields/ScriptField';
 const path = require('path');
 
 /**
@@ -324,7 +325,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 _width: 150,
                 _height: 50,
                 title: (this.layoutDoc._currentTimecode || 0).toString(),
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
             });
             this.props.addDocument?.(b);
             DocUtils.MakeLink(b, this.rootDoc, { linkRelationship: 'video snapshot' });
@@ -368,7 +369,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             _nativeHeight: Doc.NativeHeight(this.layoutDoc),
             x: NumCast(this.layoutDoc.x) + width,
             y: NumCast(this.layoutDoc.y),
-            _isLinkButton: true,
+            onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
             _width: 150,
             _height: (height / width) * 150,
             title: '--snapshot' + NumCast(this.layoutDoc._currentTimecode) + ' image-',
@@ -1010,7 +1011,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 21ccf3bc7..f23426bb3 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -116,7 +116,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
 
     public static multiValueDelimeter = ';';
     public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
-        const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === 'PARAMS' ? textBoxDoc[fieldKey] : '');
+        const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? '';
         const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
         return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
     }
@@ -223,7 +223,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                         Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true);
                     } else {
                         const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
-                        if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+                        if (!this._textBoxDoc[this._fieldKey]) {
                             const strVal = splits.length > 1 ? new List<string>(splits) : newText;
                             if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
                             Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 361e000f9..809315963 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { ComputedField } from '../../../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
@@ -1933,7 +1933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
                                 paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
                                 paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
+                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (ScriptCast(this.layoutDoc.onClick)?.script.originalScript.includes('followLink(') ? 'none' : undefined) : undefined,
                             }}
                         />
                     </div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 9a74b5dba..92696240b 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
             <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
-                    Document={this.rootDoc}
+                    Document={PresBox.targetRenderedDoc(this.rootDoc)}
                     DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1db56cbdd..3a5ba4f75 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -927,7 +927,7 @@ export namespace Doc {
     //   layout_mytemplate(somparam=somearg).
     // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
     export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
-        const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS);
+        const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || '';
         if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc;
 
         const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
@@ -936,7 +936,7 @@ export namespace Doc {
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
-        const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS';
+        const params = args.split('=').length > 1 ? args.split('=')[0] : '';
         const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
         const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']';
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
@@ -945,7 +945,7 @@ export namespace Doc {
             expandedTemplateLayout = undefined;
             _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
         } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
-            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
+            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
@@ -981,8 +981,8 @@ export namespace Doc {
             console.log('No, no, no!');
             return { layout: childDoc, data: childDoc };
         }
-        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc;
-        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc };
+        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc;
+        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '()'), data: resolvedDataDoc };
     }
 
     export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 5b489a96c..b7fd06973 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -89,7 +89,6 @@ export const documentSchema = createSchema({
     hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
     linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
     isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations
-    isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked
     layers: listSpec('string'), // which layers the document is part of
     _lockedPosition: 'boolean', // whether the document can be moved (dragged)
     _lockedTransform: 'boolean', // whether a freeformview can pan/zoom
-- 
cgit v1.2.3-70-g09d2

