From af2a2c83868c87812e9ae54c8e3cced81374619a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Feb 2023 15:08:45 -0500
Subject: restructured getAnchor()/scrollFocus to be more consistent.  added
 setterscript for computedFields. restructed getFieldsImpl to avoid making
 multiple requests for the same document due to timing issues by 'locking' a
 document cache with a promise before sending the server request.  added
 rotation and fill color as animatable fields.  fixed image cropping for

---
 src/client/views/pdf/AnchorMenu.tsx | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 4a7f5d038..fc8f1da49 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
     @observable private _showLinkPopup: boolean = false;
 
-    @observable public Highlighting: boolean = false;
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -121,9 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @action
     highlightClicked = (e: React.MouseEvent) => {
-        if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) {
-            this.Highlighting = !this.Highlighting;
-        }
+        this.Highlight(this.highlightColor, false, undefined, true);
         AnchorMenu.Instance.fadeOut(true);
     };
 
@@ -138,7 +135,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         const button = (
             <button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
                 <div className="anchor-color-preview">
-                    <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: this.Highlighting ? '' : 'rotate(-45deg)' }} />
+                    <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />
                     <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
                 </div>
             </button>
-- 
cgit v1.2.3-70-g09d2


From 8ab9236664c561d54d6a41ecb1eb2eaf6064fc0c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Mar 2023 18:21:13 -0400
Subject: changed longPress to always select and to show decorations.  fixed
 single/double-click code and cleaned up behavior timeouts.  fixed pointer
 events for tree view editing titles and using as powerpoint.

---
 src/client/documents/Documents.ts                  |  11 +-
 src/client/util/DragManager.ts                     |  34 ++--
 src/client/views/DocumentDecorations.tsx           |   3 +-
 src/client/views/InkingStroke.tsx                  |   1 -
 src/client/views/MainView.tsx                      |  17 ++
 .../collections/CollectionNoteTakingViewColumn.tsx |   4 +-
 src/client/views/collections/TabDocView.tsx        |   2 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   6 +-
 src/client/views/nodes/DocumentView.tsx            | 197 ++++++++++-----------
 src/client/views/nodes/KeyValueBox.tsx             |  24 ++-
 src/client/views/nodes/LinkBox.tsx                 |  54 +++---
 src/client/views/nodes/ScriptingBox.tsx            |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   3 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  11 +-
 src/client/views/pdf/AnchorMenu.tsx                |   2 +-
 18 files changed, 201 insertions(+), 183 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0b6d4380d..2bc1b5b1d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -228,7 +228,8 @@ export class DocumentOptions {
     contextMenuScripts?: List<ScriptField>;
     contextMenuLabels?: List<string>;
     contextMenuIcons?: List<string>;
-    allowClickBeforeDoubleClick?: boolean; // whether a click function can fire before the timeout for a double click has expired
+    defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
+    waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
     dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
     description?: string; // added for links
     layout?: string | Doc; // default layout string for a document
@@ -408,6 +409,7 @@ export namespace Docs {
                         nativeDimModifiable: true,
                         nativeHeightUnfrozen: true,
                         forceReflow: true,
+                        defaultDoubleClick: 'ignore',
                     },
                 },
             ],
@@ -436,7 +438,7 @@ export namespace Docs {
                 DocumentType.WEB,
                 {
                     layout: { view: WebBox, dataField: defaultDataKey },
-                    options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true },
+                    options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always' },
                 },
             ],
             [
@@ -578,14 +580,14 @@ export namespace Docs {
                 DocumentType.PRES,
                 {
                     layout: { view: PresBox, dataField: defaultDataKey },
-                    options: {},
+                    options: { defaultDoubleClick: 'ignore' },
                 },
             ],
             [
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
@@ -983,6 +985,7 @@ export namespace Docs {
             I.author = Doc.CurrentUserEmail;
             I.rotation = 0;
             I.data = new InkField(points);
+            I.defaultDoubleClick = 'click';
             I.creationDate = new DateField();
             I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             //I['acl-Override'] = SharingPermissions.Unset;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c35941ee5..cc116fb46 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,6 +1,6 @@
 import { action, observable, runInAction } from 'mobx';
 import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt } from '../../fields/Doc';
+import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
@@ -207,24 +207,26 @@ export namespace DragManager {
             dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
-                docDragData.droppedDocuments = await Promise.all(
-                    dragData.draggedDocuments.map(async d =>
-                        !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
-                            ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
-                            : docDragData.dropAction === 'alias'
-                            ? Doc.BestAlias(d)
-                            : docDragData.dropAction === 'proto'
-                            ? Doc.GetProto(d)
-                            : docDragData.dropAction === 'copy'
-                            ? (
-                                  await Doc.MakeClone(d)
-                              ).clone
-                            : d
+                docDragData.droppedDocuments = (
+                    await Promise.all(
+                        dragData.draggedDocuments.map(async d =>
+                            !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
+                                ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
+                                : docDragData.dropAction === 'alias'
+                                ? Doc.BestAlias(d)
+                                : docDragData.dropAction === 'proto'
+                                ? Doc.GetProto(d)
+                                : docDragData.dropAction === 'copy'
+                                ? (
+                                      await Doc.MakeClone(d)
+                                  ).clone
+                                : d
+                        )
                     )
-                );
+                ).filter(d => d);
                 !['same', 'proto'].includes(docDragData.dropAction as any) &&
                     docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
-                        const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec('string'), []);
+                        const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties);
                         const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
                         remProps.map(prop => (drop[prop] = undefined));
                     });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2982f8a99..2d2d3c2f6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -67,8 +67,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         reaction(
             () => SelectionManager.Views().slice(),
             action(docs => {
-                this._showNothing = true;
-                docs.length > 1 && (this._showNothing = false); // show decorations if multiple docs are selected
+                this._showNothing = !DocumentView.LongPress && docs.length === 1; // show decorations if multiple docs are selected or we're long pressing
                 this._editingTitle = false;
             })
         );
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 3861331b5..a085b69a5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -179,7 +179,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                       UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
                   }),
             action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                 if (doubleTap) {
                     InkStrokeProperties.Instance._controlButton = true;
                     InkStrokeProperties.Instance._currentPoint = -1;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a30d139be..c84d204d5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -214,6 +214,8 @@ export class MainView extends React.Component {
         window.removeEventListener('keyup', KeyManager.Instance.unhandle);
         window.removeEventListener('keydown', KeyManager.Instance.handle);
         window.removeEventListener('pointerdown', this.globalPointerDown, true);
+        window.removeEventListener('pointermove', this.globalPointerMove, true);
+        window.removeEventListener('mouseclick', this.globalPointerClick, true);
         window.removeEventListener('paste', KeyManager.Instance.paste as any);
         document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
     }
@@ -485,7 +487,20 @@ export class MainView extends React.Component {
         );
     }
 
+    private longPressTimer: NodeJS.Timeout | undefined;
+    globalPointerClick = action((e: any) => {
+        this.longPressTimer && clearTimeout(this.longPressTimer);
+        DocumentView.LongPress = false;
+    });
+    globalPointerMove = action((e: PointerEvent) => {
+        if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer);
+    });
     globalPointerDown = action((e: PointerEvent) => {
+        DocumentView.LongPress = false;
+        this.longPressTimer = setTimeout(
+            action(() => (DocumentView.LongPress = true)),
+            1000
+        );
         DocumentManager.removeOverlayViews();
         Doc.linkFollowUnhighlight();
         AudioBox.Enabled = true;
@@ -506,6 +521,8 @@ export class MainView extends React.Component {
         window.addEventListener('dragover', e => e.preventDefault(), false);
         // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
         document.addEventListener('pointerdown', this.globalPointerDown, true);
+        document.addEventListener('pointermove', this.globalPointerMove, true);
+        document.addEventListener('mouseclick', this.globalPointerClick, true);
         document.addEventListener(
             'click',
             (e: MouseEvent) => {
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 621e3d93b..28bdd0cb9 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -268,7 +268,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
                             {this.props.renderChildren(this.props.docList)}
                         </div>
 
-                        {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+                        {!this.props.chromeHidden ? (
                             <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
                                 <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
                                     <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
@@ -289,7 +289,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         const heading = this._heading;
         return (
             <div
-                className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+                className="collectionNoteTakingViewFieldColumn"
                 key={heading}
                 style={{
                     width: this.columnWidth,
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 631b9add5..5e6849e7c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -422,7 +422,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         this._lastView = this._view;
                     })}
                     renderDepth={0}
-                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined}
+                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                     ContainingCollectionView={undefined}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index af2d148e0..257428d56 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -305,7 +305,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument('-text-', {
+        const bullet = Docs.Create.TextDocument('', {
             layout: CollectionView.LayoutString('data'),
             title: '-title-',
             treeViewExpandedViewLock: true,
@@ -326,7 +326,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         });
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
-        FormattedTextBox.SelectOnLoad = bullet[Id];
+        DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
+
         return bullet;
     }
 
@@ -902,6 +903,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideDecorationTitle={this.props.treeView.outlineMode}
                 hideResizeHandles={this.props.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
+                onClickScriptDisable="never"
                 docViewPath={returnEmptyDoclist}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index be4c2a60e..8104ab1a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -822,7 +822,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             Doc.ActiveTool = InkTool.None;
             if (this.props.isContentActive(true)) e.stopPropagation();
-        } else if (!e.cancelBubble) {
+        } else {
+            this.props.DocumentView?.().docView?.cancelMoveEvents();
             if (this.tryDragCluster(e, this._hitCluster)) {
                 return true;
             }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c21bc9754..9e56de8c2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -127,9 +127,9 @@ export class DocumentContentsView extends React.Component<
         TraceMobx();
         if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data'));
+        if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString());
         const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string');
-        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : '');
+        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
     }
@@ -273,7 +273,7 @@ export class DocumentContentsView extends React.Component<
                 jsx={layoutFrame}
                 showWarnings={true}
                 onError={(test: any) => {
-                    console.log('DocumentContentsView:' + test);
+                    console.log('DocumentContentsView:' + test, bindings, layoutFrame);
                 }}
             />
         );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 42c2b28ba..c8f677c5f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -124,6 +124,7 @@ export interface DocComponentView {
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
+    onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     playFrom?: (time: number, endTime?: number) => void;
@@ -133,6 +134,7 @@ export interface DocComponentView {
     setFocus?: () => void; // sets input focus to the componentView
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     incrementalRendering?: () => void;
+    fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -192,6 +194,7 @@ export interface DocumentViewSharedProps {
     ignoreAutoHeight?: boolean;
     forceAutoHeight?: boolean;
     disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     pointerEvents?: () => Opt<string>;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
@@ -245,9 +248,10 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
-    private _cursorTimer: NodeJS.Timeout | undefined;
-    private _longPress = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
+    private _doubleClickTimeout: NodeJS.Timeout | undefined;
+    private _singleClickFunc: undefined | (() => any);
+    private _longPressSelector: NodeJS.Timeout | undefined;
     private _downX: number = 0;
     private _downY: number = 0;
     private _downTime: number = 0;
@@ -257,7 +261,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
-    private _timeout: NodeJS.Timeout | undefined;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -265,8 +268,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
-    @observable _pendingDoubleClick = false;
-    @observable _cursorPress = false;
 
     private get topMost() {
         return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
@@ -331,9 +332,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get nativeHeight() {
         return this.props.NativeHeight();
     }
+    @computed get disableClickScriptFunc() {
+        return (
+            DocumentView.LongPress ||
+            this.props.onClickScriptDisable === 'always' ||
+            this._componentView?.onClickScriptDisable?.() ||
+            this._componentView?.isAnyChildContentActive?.()
+        );
+    }
     @computed get onClickHandler() {
-        const noClick = this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.rootSelected() || this.props.isSelected() || this._componentView?.isAnyChildContentActive?.();
-        return noClick ? undefined : this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+        return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
     }
     @computed get onDoubleClickHandler() {
         return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
@@ -446,7 +454,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             const touch = me.touchEvent.changedTouches.item(0);
             if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
-                if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
+                if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) {
                     this.cleanUpInteractions();
                     this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined);
                 }
@@ -585,91 +593,76 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let stopPropagate = true;
             let preventDefault = true;
-            const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
             (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
-            if (this._doubleTap && (![DocumentType.FONTICON, DocumentType.PRES].includes(this.props.Document.type as any) || this.onDoubleClickHandler)) {
-                // && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
-                if (this._timeout) {
-                    clearTimeout(this._timeout);
-                    this._pendingDoubleClick = false;
-                    this._timeout = undefined;
+            if (this._doubleTap) {
+                if (this.onDoubleClickHandler?.script) {
+                    const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
+                    // prettier-ignore
+                    const func = () => this.onDoubleClickHandler.script.run( {
+                            this: this.layoutDoc,
+                            self: this.rootDoc,
+                            scriptContext: this.props.scriptContext,
+                            thisContainer: this.props.ContainingCollectionDoc,
+                            documentView: this.props.DocumentView(),
+                            clientX, clientY,  altKey,  shiftKey,  ctrlKey,
+                            value: undefined,
+                        }, console.log  );
+                    UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
+                } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) {
+                    UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
+                    SelectionManager.DeselectAll();
+                    Doc.UnBrushDoc(this.props.Document);
+                } else {
+                    this._singleClickFunc?.();
                 }
-                if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) {
-                    // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                    const { clientX, clientY, shiftKey, altKey, ctrlKey } = e;
+                this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
+                this._doubleClickTimeout = undefined;
+                this._singleClickFunc = undefined;
+            } else {
+                let clickFunc: undefined | (() => any);
+                if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
+                    const { clientX, clientY, shiftKey, altKey, metaKey } = e;
                     const func = () =>
-                        this.onDoubleClickHandler.script.run(
+                        this.onClickHandler?.script.run(
                             {
                                 this: this.layoutDoc,
                                 self: this.rootDoc,
+                                _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
                                 thisContainer: this.props.ContainingCollectionDoc,
                                 documentView: this.props.DocumentView(),
                                 clientX,
                                 clientY,
-                                altKey,
                                 shiftKey,
-                                ctrlKey,
-                                value: undefined,
+                                altKey,
+                                metaKey,
                             },
                             console.log
-                        );
-                    UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
-                } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) {
-                    //UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap');
-                    UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
-                    SelectionManager.DeselectAll();
-                    Doc.UnBrushDoc(this.props.Document);
+                        ).result?.select === true
+                            ? 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 {
+                    if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
+                        // 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
+                        stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+                    }
+                    preventDefault = false;
                 }
-            } else if (!this._longPress && this.onClickHandler?.script && !isScriptBox()) {
-                // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                const { clientX, clientY, shiftKey, altKey, metaKey } = e;
-                const func = () =>
-                    this.onClickHandler?.script.run(
-                        {
-                            this: this.layoutDoc,
-                            self: this.rootDoc,
-                            _readOnly_: false,
-                            scriptContext: this.props.scriptContext,
-                            thisContainer: this.props.ContainingCollectionDoc,
-                            documentView: this.props.DocumentView(),
-                            clientX,
-                            clientY,
-                            shiftKey,
-                            altKey,
-                            metaKey,
-                        },
-                        console.log
-                    ).result?.select === true
-                        ? this.props.select(false)
-                        : '';
-                const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
-                if (this.onDoubleClickHandler && !this.props.Document.allowClickBeforeDoubleClick) {
-                    runInAction(() => (this._pendingDoubleClick = true));
-                    this._timeout = setTimeout(() => {
-                        this._timeout = undefined;
-                        clickFunc();
-                    }, 150);
-                } else clickFunc();
-            } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                SelectionManager.DeselectAll();
-                this.allLinks.length && LinkFollower.FollowLink(undefined, this.props.Document, e.altKey);
-            } else {
-                if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
-                    // 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
-                    stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+
+                this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey));
+                if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') {
+                    this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
+                    this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
                 } else {
-                    runInAction(() => (this._pendingDoubleClick = true));
-                    this._timeout = setTimeout(
-                        action(() => {
-                            this._pendingDoubleClick = false;
-                            this._timeout = undefined;
-                        }),
-                        350
-                    );
-                    (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey);
+                    this._singleClickFunc();
+                    this._singleClickFunc = undefined;
                 }
-                preventDefault = false;
             }
             stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
@@ -678,6 +671,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000);
         if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView();
         if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
         // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
@@ -689,13 +683,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             return;
         }
-        this._cursorTimer = setTimeout(
-            action(() => {
-                this._cursorPress = true;
-                this.props.select(false);
-            }),
-            1000 // long press required duration
-        );
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
@@ -719,44 +706,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             document.removeEventListener('pointerup', this.onPointerUp);
             document.addEventListener('pointerup', this.onPointerUp);
-        } else {
-            this._cursorTimer && clearTimeout(this._cursorTimer);
-            this._cursorPress = false;
         }
     };
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (e.cancelBubble) return;
+        if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
+        if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
-                    document.removeEventListener('pointermove', this.onPointerMove);
-                    document.removeEventListener('pointerup', this.onPointerUp);
-                    this._cursorTimer && clearTimeout(this._cursorTimer);
-                    this._cursorPress = false;
-                    this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
-                }
+                this.cleanupPointerEvents();
+                this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
         }
     };
 
+    cancelMoveEvents = () => document.removeEventListener('pointermove', this.onPointerMove);
+
     cleanupPointerEvents = () => {
         this.cleanUpInteractions();
-        document.removeEventListener('pointermove', this.onPointerMove);
+        this.cancelMoveEvents();
         document.removeEventListener('pointerup', this.onPointerUp);
     };
 
     @action
     onPointerUp = (e: PointerEvent): void => {
         this.cleanupPointerEvents();
-        this._longPress = this._cursorPress;
-        this._cursorTimer && clearTimeout(this._cursorTimer);
-        this._cursorPress = false;
+        this._longPressSelector && clearTimeout(this._longPressSelector);
 
         const now = Date.now();
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
@@ -766,6 +745,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             // bcz: this is a placeholder.  documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
             if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
+        if (DocumentView.LongPress) e.preventDefault();
     };
 
     @undoBatch
@@ -1051,7 +1031,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         // Add link to help documentation
         if (documentationDescription && documentationLink) {
-            console.log('add documentation item');
             helpItems.push({
                 description: documentationDescription,
                 event: () => {
@@ -1078,7 +1057,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    onClickFunc = () => this.onClickHandler as any as ScriptField; // bcz: typing HACK.  check and fix.
+    onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
     isContentActive = (outsideReaction?: boolean) => {
@@ -1101,7 +1080,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
                 .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
         const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
-        return !this.props.isSelected() &&
+        return !this.props.LayoutTemplateString &&
+            !this.props.isSelected() &&
             LightboxView.LightboxDoc !== this.rootDoc &&
             this.thumb &&
             !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
@@ -1129,7 +1109,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </Tooltip>
         );
     }
-    contentPointerEvents = () => (this.onClickHandler ? 'none' : this.pointerEvents);
+    contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
     @computed get contents() {
         TraceMobx();
         return (
@@ -1326,7 +1306,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                     minWidth: 50 * ffscale(),
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
@@ -1360,7 +1340,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     width: !this.headerMargin ? `calc(100% - 18px)` : '100%', // leave room for annotation button
                     color: lightOrDark(background),
                     background,
-                    pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
                 <EditableView
                     ref={this._titleRef}
@@ -1420,7 +1400,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         ...style,
                         background: isButton || thumb ? undefined : this.backgroundColor,
                         opacity: this.opacity,
-                        cursor: this._cursorPress ? 'wait' : Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
+                        cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
                         color: StrCast(this.layoutDoc.color, 'inherit'),
                         fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
                         fontSize: Cast(this.Document._fontSize, 'string', null),
@@ -1428,7 +1408,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                     }}>
                     {this.innards}
-                    {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
+                    {!this.disableClickScriptFunc && this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
                     {this.widgetDecorations ?? null}
                 </div>
             )
@@ -1530,6 +1510,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    @observable public static LongPress = false;
     public get displayName() {
         return 'DocumentView(' + this.props.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
@@ -1628,7 +1609,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     get fitWidth() {
-        return this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth;
+        return this.docView?._componentView?.fitWidth?.() ?? this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth;
     }
 
     @computed get hideLinkButton() {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 0b1de0ff6..9c31ed3e4 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -3,24 +3,24 @@ import { observer } from 'mobx-react';
 import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
 import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImageBox } from './ImageBox';
 import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import React = require('react');
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
 import e = require('express');
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { ImageBox } from './ImageBox';
-import { OpenWhere } from './DocumentView';
+import { returnTrue } from '../../../Utils';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -30,8 +30,8 @@ export type KVPScript = {
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string) {
-        return FieldView.LayoutString(KeyValueBox, fieldStr);
+    public static LayoutString() {
+        return FieldView.LayoutString(KeyValueBox, 'data');
     }
 
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -39,6 +39,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     private _keyInput = React.createRef<HTMLInputElement>();
     private _valInput = React.createRef<HTMLInputElement>();
 
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
+    onClickScriptDisable: () => 'always' = () => 'always';
+    fitWidth = returnTrue;
+
     @observable private rows: KeyValuePair[] = [];
 
     @computed get splitPercentage() {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 43f4b43fb..470f7e803 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,29 +1,39 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { emptyFunction, returnFalse } from "../../../Utils";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { StyleProp } from "../StyleProvider";
-import { ComparisonBox } from "./ComparisonBox";
+import React = require('react');
+import { observer } from 'mobx-react';
+import { emptyFunction, returnFalse, returnTrue } from '../../../Utils';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { StyleProp } from '../StyleProvider';
+import { ComparisonBox } from './ComparisonBox';
 import { FieldView, FieldViewProps } from './FieldView';
-import "./LinkBox.scss";
+import './LinkBox.scss';
 
 @observer
 export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(LinkBox, fieldKey);
+    }
     isContentActiveFunc = () => this.isContentActive();
+
+    onClickScriptDisable: () => 'always' = () => 'always';
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
     render() {
-        if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true);
-        return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`}
-            style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} >
-            <ComparisonBox {...this.props}
-                fieldKey="anchor"
-                setHeight={emptyFunction}
-                dontRegisterView={true}
-                renderDepth={this.props.renderDepth + 1}
-                isContentActive={this.isContentActiveFunc}
-                addDocument={returnFalse}
-                removeDocument={returnFalse}
-                moveDocument={returnFalse} />
-        </div>;
+        if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true));
+        return (
+            <div className={`linkBox-container${this.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
+                <ComparisonBox
+                    {...this.props}
+                    fieldKey="anchor"
+                    setHeight={emptyFunction}
+                    dontRegisterView={true}
+                    renderDepth={this.props.renderDepth + 1}
+                    isContentActive={this.isContentActiveFunc}
+                    addDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    moveDocument={returnFalse}
+                />
+            </div>
+        );
     }
-} 
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index a6e2c3e90..5683b0fe7 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString } from '../../../Utils';
+import { returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
@@ -114,6 +114,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         }
     }
 
+    onClickScriptDisable: () => 'always' = () => 'always';
+
     @action
     componentDidMount() {
         this.props.setContentView?.(this);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f7425b26a..73283263f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -205,7 +205,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     this._webPageHasBeenRendered = true;
                 } else if (
                     (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
-                    !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
                     LightboxView.LightboxDoc !== this.rootDoc
                 ) {
                     // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
@@ -877,7 +876,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @computed get content() {
-        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
+        const interactive = this.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
         return (
             <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}>
                 {this.urlContent}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d9364e5b5..468bcc4d8 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -817,7 +817,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
             setMode: () => SetActiveInkWidth(value.toString()),
         }],
         ['strokeColor', {
-            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()),
+            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
             setInk: (doc: Doc) => (doc.color = String(value)),
             setMode: () => SetActiveInkColor(StrCast(value)),
         }],
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ddec86606..3ce2366f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1477,7 +1477,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             e.preventDefault();
         }
     };
-    onSelectMove = (e: PointerEvent) => e.stopPropagation();
+    onSelectMove = (e: PointerEvent) => {
+        this.props.DocumentView?.().docView?.cancelMoveEvents();
+        e.stopPropagation();
+    };
     onSelectEnd = (e: PointerEvent) => {
         document.removeEventListener('pointerup', this.onSelectEnd);
         document.removeEventListener('pointermove', this.onSelectMove);
@@ -1497,10 +1500,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return;
             }
         }
-
-        if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
-            e.stopPropagation();
-        }
     };
     @action
     onDoubleClick = (e: React.MouseEvent): void => {
@@ -1566,8 +1565,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this.props.isSelected(true)) {
             // if text box is selected, then it consumes all click events
             (e.nativeEvent as any).handledByInnerReactInstance = true;
-            if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox.  Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
-            // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
             this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
         }
         this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index fc8f1da49..7392d2706 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -80,7 +80,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             { fireImmediately: true }
         );
         this._disposer = reaction(
-            () => SelectionManager.Views(),
+            () => SelectionManager.Views().slice(),
             selected => {
                 this._showLinkPopup = false;
                 AnchorMenu.Instance.fadeOut(true);
-- 
cgit v1.2.3-70-g09d2

