From d58195a0470b3882d6b43b18f1f4ab7a373a671f Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sat, 12 Oct 2019 17:02:31 -0400
Subject: marquee now relies on pdf-style menu. pdf-style menu is now
 componentized so that other things can use it

---
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      | 52 ++++++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx

(limited to 'src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
new file mode 100644
index 000000000..7c5b87283
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -0,0 +1,52 @@
+import React = require("react")
+import AntimodeMenu from "../../AntimodeMenu";
+import { observer } from "mobx-react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { unimplementedFunction } from "../../../../Utils";
+
+@observer
+export default class MarqueeOptionsMenu extends AntimodeMenu {
+    static Instance: MarqueeOptionsMenu;
+
+    public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+    public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+    public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+    public showMarquee: () => void = unimplementedFunction;
+    public hideMarquee: () => void = unimplementedFunction;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        MarqueeOptionsMenu.Instance = this;
+    }
+
+    render() {
+        let buttons = [
+            <button
+                className="antimodeMenu-button"
+                title="Create a Collection"
+                onPointerDown={this.createCollection}
+                onPointerLeave={this.hideMarquee}
+                onPointerEnter={this.showMarquee}>
+                <FontAwesomeIcon icon="object-group" size="lg" />
+            </button>,
+            <button
+                className="antimodeMenu-button"
+                title="Summarize Documents"
+                onPointerDown={this.summarize}
+                onPointerLeave={this.hideMarquee}
+                onPointerEnter={this.showMarquee}>
+                <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+            </button>,
+            <button
+                className="antimodeMenu-button"
+                title="Delete Documents"
+                onPointerDown={this.delete}
+                onPointerLeave={this.hideMarquee}
+                onPointerEnter={this.showMarquee}>
+                <FontAwesomeIcon icon="trash-alt" size="lg" />
+            </button>,
+        ]
+        return this.getElement(buttons);
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From ab959c44ea6393783fcfef82a241a7bc3e9124ed Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sun, 13 Oct 2019 14:29:59 -0400
Subject: improved marquee menu interactions, fixed issue with panning on cffv

---
 .../collectionFreeForm/CollectionFreeFormView.scss     |  3 +++
 .../collectionFreeForm/MarqueeOptionsMenu.tsx          | 12 +++---------
 .../collections/collectionFreeForm/MarqueeView.tsx     | 18 +++++++++++++++---
 3 files changed, 21 insertions(+), 12 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index a20d6df3a..9b608a576 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -26,6 +26,9 @@
 }
 
 .collectionfreeformview-container {
+    // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
+    touch-action: none;
+
     .collectionfreeformview>.jsx-parser {
         position: inherit;
         height: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 7c5b87283..91fcad4be 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -25,25 +25,19 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
             <button
                 className="antimodeMenu-button"
                 title="Create a Collection"
-                onPointerDown={this.createCollection}
-                onPointerLeave={this.hideMarquee}
-                onPointerEnter={this.showMarquee}>
+                onPointerDown={this.createCollection}>
                 <FontAwesomeIcon icon="object-group" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Summarize Documents"
-                onPointerDown={this.summarize}
-                onPointerLeave={this.hideMarquee}
-                onPointerEnter={this.showMarquee}>
+                onPointerDown={this.summarize}>
                 <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Delete Documents"
-                onPointerDown={this.delete}
-                onPointerLeave={this.hideMarquee}
-                onPointerEnter={this.showMarquee}>
+                onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
         ]
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ad4da7733..4a7fa629c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -51,13 +51,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     @action
-    cleanupInteractions = (all: boolean = false) => {
+    cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => {
         if (all) {
             document.removeEventListener("pointerup", this.onPointerUp, true);
             document.removeEventListener("pointermove", this.onPointerMove, true);
         }
         document.removeEventListener("keydown", this.marqueeCommand, true);
-        this._visible = false;
+        if (hideMarquee) {
+            this._visible = false;
+        }
     }
 
     @undoBatch
@@ -204,7 +206,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
             MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
-        this.cleanupInteractions(true);
+        this.cleanupInteractions(true, this._commandExecuted);
+
+        let hideMarquee = () => {
+            this.hideMarquee();
+            MarqueeOptionsMenu.Instance.fadeOut(true);
+            document.removeEventListener("pointerdown", hideMarquee);
+        }
+        document.addEventListener("pointerdown", hideMarquee)
 
         if (e.altKey) {
             e.preventDefault();
@@ -283,6 +292,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         SelectionManager.DeselectAll();
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
+        this.hideMarquee();
     }
 
     getCollection = (selected: Doc[]) => {
@@ -321,6 +331,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
         dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
         this.marqueeInkDelete(inkData);
+        this.hideMarquee();
         return newCollection;
     }
 
@@ -341,6 +352,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         this.props.addDocument(newCollection, false);
         this.props.selectDocuments([newCollection]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
+        this.hideMarquee();
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 667d196a2cbc8e237de5be9a6f7ec4ecca9d2bb5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 19 Nov 2019 16:05:42 -0500
Subject: tried to simplify button bar menu

---
 src/client/views/DocumentButtonBar.tsx             | 104 +++------------------
 src/client/views/InkSelectDecorations.tsx          |  16 ++--
 src/client/views/TemplateMenu.tsx                  |  49 +++++++++-
 .../views/collections/CollectionStaffView.tsx      |  14 ++-
 src/client/views/collections/CollectionView.tsx    |   4 +-
 .../views/collections/ParentDocumentSelector.scss  |   6 ++
 .../views/collections/ParentDocumentSelector.tsx   |  29 ++++--
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FormattedTextBox.scss       |   1 -
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 11 files changed, 104 insertions(+), 127 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1412316f9..c7ee413c9 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -8,13 +8,12 @@ import { RichTextField } from '../../new_fields/RichTextField';
 import { NumCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
+import { DragManager } from "../util/DragManager";
 import { LinkManager } from '../util/LinkManager';
 import { UndoManager } from "../util/UndoManager";
 import './DocumentButtonBar.scss';
 import './collections/ParentDocumentSelector.scss';
 import { LinkMenu } from "./linking/LinkMenu";
-import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
@@ -43,7 +42,6 @@ const fetch: IconProp = "sync-alt";
 @observer
 export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
-    private _linkerButton = React.createRef<HTMLDivElement>();
     private _aliasButton = React.createRef<HTMLDivElement>();
     private _tooltipoff = React.createRef<HTMLDivElement>();
     private _textDoc?: Doc;
@@ -109,14 +107,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         document.addEventListener("pointerup", this.onLinkerButtonUp);
     }
 
-    onAliasButtonDown = (e: React.PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.addEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        document.addEventListener("pointerup", this.onAliasButtonUp);
-    }
 
     onLinkerButtonUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onLinkerButtonMoved);
@@ -124,22 +114,17 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         e.stopPropagation();
     }
 
-    onAliasButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        e.stopPropagation();
-    }
 
     @action
     onLinkerButtonMoved = (e: PointerEvent): void => {
-        if (this._linkerButton.current !== null) {
+        if (this._linkButton.current !== null) {
             document.removeEventListener("pointermove", this.onLinkerButtonMoved);
-            document.removeEventListener("pointerup", this.onLinkerButtonUp);
+            document.removeEventListener("pointerup", this.onLinkButtonUp);
             let docView = this.props.views[0];
             let container = docView.props.ContainingCollectionDoc ? docView.props.ContainingCollectionDoc.proto : undefined;
             let dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []);
             let linkDrag = UndoManager.StartBatch("Drag Link");
-            DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
+            DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
                     dragComplete: () => {
                         let tooltipmenu = FormattedTextBox.ToolTipTextMenu;
@@ -163,62 +148,22 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         e.stopPropagation();
     }
 
-    @action
-    onAliasButtonMoved = (e: PointerEvent): void => {
-        if (this._aliasButton.current !== null) {
-            document.removeEventListener("pointermove", this.onAliasButtonMoved);
-            document.removeEventListener("pointerup", this.onAliasButtonUp);
-
-            let dragDocView = this.props.views[0];
-            let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
-            const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
-            dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top);
-            dragData.embedDoc = true;
-            dragData.dropAction = "alias";
-            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, {
-                offsetX: dragData.offset[0],
-                offsetY: dragData.offset[1],
-                handlers: {
-                    dragComplete: action(emptyFunction),
-                },
-                hideSource: false
-            });
-        }
-        e.stopPropagation();
-    }
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.addEventListener("pointermove", this.onLinkButtonMoved);
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+        document.addEventListener("pointermove", this.onLinkerButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         e.stopPropagation();
     }
 
-    onLinkButtonMoved = async (e: PointerEvent) => {
-        if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved);
-            document.removeEventListener("pointerup", this.onLinkButtonUp);
-            DragLinksAsDocuments(this._linkButton.current, e.x, e.y, this.props.views[0].props.Document);
-        }
-        e.stopPropagation();
-    }
-
-    aliasDragger = () => {
-        return (<div className="linkButtonWrapper">
-            <div title="Drag Alias" className="linkButton-linker" ref={this._aliasButton} onPointerDown={this.onAliasButtonDown}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
-            </div>
-        </div>);
-    }
-
     private get targetDoc() {
         return this.props.views[0].props.Document;
     }
@@ -317,30 +262,18 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         }
     }
 
-    get metadataMenu() {
-        return (
-            <div className="linkButtonWrapper">
-                <Flyout anchorPoint={anchorPoints.TOP_LEFT}
-                    content={<MetadataEntryMenu docs={() => this.props.views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
-                    <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
-                </Flyout>
-            </div>
-        );
-    }
-
     render() {
         let linkButton = null;
         if (this.props.views.length > 0) {
             let selFirst = this.props.views[0];
 
             let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
-            linkButton = (<Flyout
-                anchorPoint={anchorPoints.RIGHT_TOP}
-                content={<LinkMenu docView={selFirst}
-                    addDocTab={selFirst.props.addDocTab}
-                    changeFlyout={emptyFunction} />}>
-                <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
-            </Flyout >);
+            linkButton = <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+                content={<LinkMenu docView={selFirst} addDocTab={selFirst.props.addDocTab} changeFlyout={emptyFunction} />}>
+                <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
+                    {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+                </div>
+            </Flyout >;
         }
 
         let templates: Map<Template, boolean> = new Map();
@@ -349,21 +282,14 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
 
         return (<div className="documentButtonBar">
             <div className="linkButtonWrapper">
-                <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
-            </div>
-            <div className="linkButtonWrapper">
-                <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
-                    <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
-                </div>
+                <div title="Drag(create link) Tap(view links)" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
             </div>
             <div className="linkButtonWrapper">
                 <TemplateMenu docs={this.props.views} templates={templates} />
             </div>
-            {this.metadataMenu}
-            {this.aliasDragger()}
             {this.considerGoogleDocsPush()}
             {this.considerGoogleDocsPull()}
-            <ParentDocSelector Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
+            <ParentDocSelector Views={this.props.views} Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
                 where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight");
                 return true;
             }} />
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
index 95ccc1777..d40df9b75 100644
--- a/src/client/views/InkSelectDecorations.tsx
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -3,7 +3,7 @@ import { Touchable } from "./Touchable";
 import { PointData } from "../../new_fields/InkField";
 import { observer } from "mobx-react";
 import { computed, observable, action, runInAction } from "mobx";
-import "./InkSelectDecorations.scss"
+import "./InkSelectDecorations.scss";
 
 @observer
 export default class InkSelectDecorations extends Touchable {
@@ -46,14 +46,10 @@ export default class InkSelectDecorations extends Touchable {
 
     render() {
         let bounds = this.Bounds;
-        return (
-            <div style={{
-                top: bounds.y, left: bounds.x,
-                height: bounds.b - bounds.y,
-                width: bounds.r - bounds.x
-            }}>
-
-            </div>
-        )
+        return <div style={{
+            top: bounds.y, left: bounds.x,
+            height: bounds.b - bounds.y,
+            width: bounds.r - bounds.x
+        }} />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 96265385e..1df5f49c4 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,6 +10,7 @@ import { Template, Templates } from "./Templates";
 import React = require("react");
 import { Doc } from "../../new_fields/Doc";
 import { StrCast } from "../../new_fields/Types";
+import { emptyFunction } from "../../Utils";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -46,10 +47,13 @@ export interface TemplateMenuProps {
     templates: Map<Template, boolean>;
 }
 
+
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
-    dragRef = React.createRef<HTMLUListElement>();
+    private _downx = 0;
+    private _downy = 0;
+    private _dragRef = React.createRef<HTMLUListElement>();
 
     toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => {
         this.props.docs.map(dv => dv.setCustomView(e.target.checked));
@@ -122,6 +126,43 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
+    onAliasButtonUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onAliasButtonMoved);
+        document.removeEventListener("pointerup", this.onAliasButtonUp);
+        e.stopPropagation();
+    }
+
+    onAliasButtonDown = (e: React.PointerEvent): void => {
+        this._downx = e.clientX;
+        this._downy = e.clientY;
+        e.stopPropagation();
+        e.preventDefault();
+        document.removeEventListener("pointermove", this.onAliasButtonMoved);
+        document.addEventListener("pointermove", this.onAliasButtonMoved);
+        document.removeEventListener("pointerup", this.onAliasButtonUp);
+        document.addEventListener("pointerup", this.onAliasButtonUp);
+    }
+    onAliasButtonMoved = (e: PointerEvent): void => {
+        if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
+            document.removeEventListener("pointermove", this.onAliasButtonMoved);
+            document.removeEventListener("pointerup", this.onAliasButtonUp);
+
+            let dragDocView = this.props.docs[0];
+            let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
+            const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            dragData.embedDoc = true;
+            dragData.dropAction = "alias";
+            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
+                offsetX: dragData.offset[0],
+                offsetY: dragData.offset[1],
+                handlers: {
+                    dragComplete: action(emptyFunction),
+                },
+                hideSource: false
+            });
+        }
+        e.stopPropagation();
+    }
 
     render() {
         let layout = Doc.Layout(this.props.docs[0].Document);
@@ -132,9 +173,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         return (
-            <div className="templating-menu" >
-                <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
-                <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
+            <div className="templating-menu" onPointerDown={this.onAliasButtonDown}>
+                <div title="Drag:(create alias). Tap:(modify layout)." className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+                <ul id="template-list" ref={this._dragRef} style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
                     {<button onClick={this.clearTemplates}>Restore Defaults</button>}
                 </ul>
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index eea05ea61..40e860b12 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -1,6 +1,6 @@
 import { CollectionSubView } from "./CollectionSubView";
 import { Transform } from "../../util/Transform";
-import React = require("react")
+import React = require("react");
 import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
 import { Doc, HeightSym } from "../../../new_fields/Doc";
 import { NumCast } from "../../../new_fields/Types";
@@ -36,7 +36,7 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
         for (let i = 0; i < this._staves; i++) {
             let rows = [];
             for (let j = 0; j < 5; j++) {
-                rows.push(<div key={`staff-${i}-${j}`} className="collectionStaffView-line"></div>)
+                rows.push(<div key={`staff-${i}-${j}`} className="collectionStaffView-line"></div>);
             }
             staves.push(<div key={`staff-${i}`} className="collectionStaffView-staff">
                 {rows}
@@ -51,11 +51,9 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
     }
 
     render() {
-        return (
-            <div className="collectionStaffView" ref={this._mainCont}>
-                {this.staves}
-                {this.addStaffButton}
-            </div>
-        )
+        return <div className="collectionStaffView" ref={this._mainCont}>
+            {this.staves}
+            {this.addStaffButton}
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8f1278670..347fa7d0d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -168,7 +168,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
             case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
             case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
-            case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />)
+            case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
             case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
@@ -229,7 +229,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             let more = ContextMenu.Instance.findByDescription("More...");
             let moreItems = more && "subitems" in more ? more.subitems : [];
             moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-            !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" })
+            !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
         }
     }
 
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index c186d15f8..aa25a900c 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -23,6 +23,12 @@
 .parentDocumentSelector-button {
     pointer-events: all;
 }
+.parentDocumentSelector-metadata {
+    pointer-events: auto;
+    padding-right: 5px;
+    width: 25px;
+    display: inline-block;
+}
 .buttonSelector {
     position: absolute; 
     display: inline-block; 
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 8b6fa330c..ba83630a4 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -13,10 +13,14 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { library } from "@fortawesome/fontawesome-svg-core";
+import { MetadataEntryMenu } from "../MetadataEntryMenu";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
 
 library.add(faEdit);
 
-type SelectorProps = { Document: Doc, Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
+type SelectorProps = { Document: Doc, Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
 @observer
 export class SelectorContextMenu extends React.Component<SelectorProps> {
     @observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -53,16 +57,23 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
             this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
         };
     }
+    get metadataMenu() {
+        return <div className="parentDocumentSelector-metadata">
+            <Flyout anchorPoint={anchorPoints.TOP_LEFT}
+                content={<MetadataEntryMenu docs={() => this.props.Views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
+                <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
+            </Flyout>
+        </div>;
+    }
 
     render() {
-        return (
-            <>
-                <p key="contexts">Contexts:</p>
-                {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
-                {this._otherDocs.length ? <hr key="hr" /> : null}
-                {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
-            </>
-        );
+        return <div >
+            <div key="metadata">Metadata: {this.metadataMenu}</div>
+            <p key="contexts">Contexts:</p>
+            {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+            {this._otherDocs.length ? <hr key="hr" /> : null}
+            {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+        </div>;
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 91fcad4be..28ddc19d7 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import AntimodeMenu from "../../AntimodeMenu";
 import { observer } from "mobx-react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -40,7 +40,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
                 onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
-        ]
+        ];
         return this.getElement(buttons);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 865ec8d9b..411d6bdea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -537,7 +537,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (showTitle ? 25 : 0) + 1;
     }
 
-    @computed get finalLayoutKey() { return this.props.layoutKey || "layout" }
+    @computed get finalLayoutKey() { return this.props.layoutKey || "layout"; }
     childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     @computed get contents() {
         trace();
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index f7b6e92d9..269a3ca68 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -45,7 +45,6 @@
     position: relative;
     overflow: auto;
     display: inline-block;
-    padding: 10px 10px;
     width: 100%;
     height: 100%;
 }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ec8d8be11..77790a708 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -654,7 +654,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
                 chromeCollapsed={true}>
             </CollectionFreeFormView>
-        </div>
+        </div>;
     }
     @computed get pdfViewerDiv() {
         return <div className={"pdfViewer-text" + ((!DocumentDecorations.Instance.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />;
-- 
cgit v1.2.3-70-g09d2

