From 59c52553a942d327b3cdf7377eb85f4b0b19c2dd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 May 2019 23:52:13 -0400
Subject: added imageResize handle in text boxes.

---
 src/client/util/TooltipTextMenu.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 68a73375e..e33c53a1a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -94,6 +94,7 @@ export class TooltipTextMenu {
         this.fontSizeToNum = new Map();
         this.fontSizeToNum.set(schema.marks.p10, 10);
         this.fontSizeToNum.set(schema.marks.p12, 12);
+        this.fontSizeToNum.set(schema.marks.p14, 14);
         this.fontSizeToNum.set(schema.marks.p16, 16);
         this.fontSizeToNum.set(schema.marks.p24, 24);
         this.fontSizeToNum.set(schema.marks.p32, 32);
-- 
cgit v1.2.3-70-g09d2


From 9015421a227ab58f309906eabf884654d3a31a17 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 11:58:36 -0400
Subject: added start of hyperlinks to text boxes

---
 src/client/util/DragManager.ts          |   3 +-
 src/client/util/TooltipTextMenu.tsx     | 101 +++++++++++++++++++++++++++++++-
 src/client/views/nodes/DocumentView.tsx |   1 +
 3 files changed, 102 insertions(+), 3 deletions(-)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ec192eaff..fbf20e244 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -172,6 +172,7 @@ export namespace DragManager {
         droppedDocuments: Doc[] = [];
         linkSourceDocument: Doc;
         blacklist: Doc[];
+        dontClearTextBox?: boolean;
         [id: string]: any;
     }
 
@@ -188,7 +189,7 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        MainOverlayTextBox.Instance.SetTextDoc();
+        if (!dragData.dontClearTextBox) MainOverlayTextBox.Instance.SetTextDoc();
 
         let scaleXs: number[] = [];
         let scaleYs: number[] = [];
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e33c53a1a..15895fd1c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -6,19 +6,28 @@ import { keymap } from "prosemirror-keymap";
 import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
-import { Schema, NodeType, MarkType } from "prosemirror-model";
+import { Schema, NodeType, MarkType, Mark } from "prosemirror-model";
 import React = require("react");
 import "./TooltipTextMenu.scss";
 const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
-import { liftTarget } from 'prosemirror-transform';
+import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
 import {
     faListUl,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { throwStatement } from "babel-types";
+import { View } from "@react-pdf/renderer";
+import { DragManager } from "./DragManager";
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/RefField";
+import { Utils } from "../northstar/utils/Utils";
+import { DocServer } from "../DocServer";
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocumentManager } from "./DocumentManager";
 
 const SVG = "http://www.w3.org/2000/svg";
 
@@ -37,6 +46,8 @@ export class TooltipTextMenu {
     private fontStylesToName: Map<MarkType, string>;
     private listTypeToIcon: Map<NodeType, string>;
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+    private linkEditor?: HTMLDivElement;
+    private linkText?: HTMLDivElement;
     //dropdown doms
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
@@ -150,6 +161,90 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this.fontStyleDom);
     }
 
+    updateLinkMenu() {
+        if (!this.linkEditor || !this.linkText) {
+            this.linkEditor = document.createElement("div");
+            this.linkEditor.style.color = "white";
+            this.linkText = document.createElement("div");
+            this.linkText.style.cssFloat = "left";
+            this.linkText.style.marginRight = "5px";
+            this.linkText.setAttribute("contenteditable", "true");
+            this.linkText.style.color = "white";
+            let linkBtn = document.createElement("div");
+            linkBtn.textContent = ">>";
+            linkBtn.style.width = "20px";
+            linkBtn.style.height = "20px";
+            linkBtn.style.color = "white";
+            linkBtn.style.cssFloat = "left";
+            linkBtn.onpointerdown = (e: PointerEvent) => {
+                let node = this.view.state.selection.$from.nodeAfter;
+                let link = node && node.marks.find(m => m.type.name === "link");
+                if (link) {
+                    console.log("Link to : " + link.attrs.href);
+                    let href: string = link.attrs.href;
+                    if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+                        let docid = href.replace(DocServer.prepend("/doc/"), "");
+                        DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                            if (f instanceof Doc) {
+                                if (DocumentManager.Instance.getDocumentView(f))
+                                    DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                                else CollectionDockingView.Instance.AddRightSplit(f);
+                            }
+                        }));
+                    }
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            }
+            let linkDrag = document.createElement("div");
+            linkDrag.textContent = "O";
+            linkDrag.style.width = "20px";
+            linkDrag.style.height = "20px";
+            linkDrag.style.color = "white";
+            linkDrag.style.cssFloat = "left";
+            linkDrag.onpointerdown = (e: PointerEvent) => {
+                let dragData = new DragManager.LinkDragData(this.editorProps.Document);
+                dragData.dontClearTextBox = true;
+                DragManager.StartLinkDrag(this.linkEditor!, dragData, e.clientX, e.clientY,
+                    {
+                        handlers: {
+                            dragComplete: action(() => {
+                                let m = dragData.droppedDocuments as Doc[];
+                                this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                            }),
+                        },
+                        hideSource: false
+                    })
+            };
+            this.linkEditor.appendChild(this.linkText);
+            this.linkEditor.appendChild(linkBtn);
+            this.linkEditor.appendChild(linkDrag)
+            this.tooltip.appendChild(this.linkEditor);
+        }
+
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = node && node.marks.find(m => m.type.name === "link");
+        this.linkText.textContent = link ? link.attrs.href : "-empty-";
+
+        this.linkText.onkeydown = (e: KeyboardEvent) => {
+            if (e.key === "Enter") {
+                this.makeLink(this.linkText!.textContent!);
+                e.stopPropagation();
+                e.preventDefault();
+            }
+        }
+        this.tooltip.appendChild(this.linkEditor);
+    }
+
+    makeLink = (target: string) => {
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+        node = this.view.state.selection.$from.nodeAfter;
+        link = node && node.marks.find(m => m.type.name === "link");
+    }
+
     //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
     updateListItemDropdown(label: string, listTypeBtn: Node) {
         //remove old btn
@@ -348,6 +443,8 @@ export class TooltipTextMenu {
         } else { //multiple font sizes selected
             this.updateFontSizeDropdown("Various");
         }
+
+        this.updateLinkMenu();
     }
 
     //finds all active marks on selection in given group
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 38efeeba5..c3bf36553 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -238,6 +238,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
             Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 244d4d127e7e1a0faadbc5a8baed7922ef03522c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 14:11:35 -0400
Subject: cleaned up textlinking a little.

---
 src/client/util/DragManager.ts                     | 13 ++--
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 25 +++---
 src/client/views/DocumentDecorations.tsx           |  8 +-
 src/client/views/MainOverlayTextBox.tsx            | 30 ++++---
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +-
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 91 ++++++++++++++++++----
 10 files changed, 122 insertions(+), 64 deletions(-)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index fbf20e244..eaf851a75 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,11 +1,9 @@
-import { action } from "mobx";
+import { action, runInAction } from "mobx";
+import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Cast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { Cast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
@@ -154,7 +152,10 @@ export namespace DragManager {
         [id: string]: any;
     }
 
+    export let StartDragFunctions: (() => void)[] = [];
+
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+        runInAction(() => StartDragFunctions.map(func => func()));
         StartDrag(eles, dragData, downX, downY, options,
             (dropData: { [id: string]: any }) =>
                 (dropData.droppedDocuments = dragData.userDropAction == "alias" || (!dragData.userDropAction && dragData.dropAction == "alias") ?
@@ -189,7 +190,6 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        if (!dragData.dontClearTextBox) MainOverlayTextBox.Instance.SetTextDoc();
 
         let scaleXs: number[] = [];
         let scaleYs: number[] = [];
@@ -217,6 +217,7 @@ export namespace DragManager {
             dragElement.style.top = "0";
             dragElement.style.bottom = "";
             dragElement.style.left = "0";
+            dragElement.style.color = "black";
             dragElement.style.transformOrigin = "0 0";
             dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
             dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index fe5acf4b4..a3a8172c7 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,7 +1,7 @@
 import { observable, action } from "mobx";
 import { Doc } from "../../new_fields/Doc";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 export namespace SelectionManager {
     class Manager {
@@ -25,7 +25,7 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
-            MainOverlayTextBox.Instance.SetTextDoc();
+            FormattedTextBox.InputBoxOverlay = undefined;
         }
         @action
         ReselectAll() {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 15895fd1c..6eb654319 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -48,6 +48,7 @@ export class TooltipTextMenu {
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
     private linkEditor?: HTMLDivElement;
     private linkText?: HTMLDivElement;
+    private linkDrag?: HTMLImageElement;
     //dropdown doms
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
@@ -168,8 +169,13 @@ export class TooltipTextMenu {
             this.linkText = document.createElement("div");
             this.linkText.style.cssFloat = "left";
             this.linkText.style.marginRight = "5px";
+            this.linkText.style.marginLeft = "5px";
             this.linkText.setAttribute("contenteditable", "true");
+            this.linkText.style.whiteSpace = "nowrap";
+            this.linkText.style.width = "150px";
+            this.linkText.style.overflow = "hidden";
             this.linkText.style.color = "white";
+            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
             linkBtn.style.width = "20px";
@@ -196,16 +202,17 @@ export class TooltipTextMenu {
                     e.preventDefault();
                 }
             }
-            let linkDrag = document.createElement("div");
-            linkDrag.textContent = "O";
-            linkDrag.style.width = "20px";
-            linkDrag.style.height = "20px";
-            linkDrag.style.color = "white";
-            linkDrag.style.cssFloat = "left";
-            linkDrag.onpointerdown = (e: PointerEvent) => {
+            this.linkDrag = document.createElement("img");
+            this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+            this.linkDrag.style.width = "20px";
+            this.linkDrag.style.height = "20px";
+            this.linkDrag.style.color = "white";
+            this.linkDrag.style.background = "black";
+            this.linkDrag.style.cssFloat = "left";
+            this.linkDrag.onpointerdown = (e: PointerEvent) => {
                 let dragData = new DragManager.LinkDragData(this.editorProps.Document);
                 dragData.dontClearTextBox = true;
-                DragManager.StartLinkDrag(this.linkEditor!, dragData, e.clientX, e.clientY,
+                DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
                     {
                         handlers: {
                             dragComplete: action(() => {
@@ -216,9 +223,9 @@ export class TooltipTextMenu {
                         hideSource: false
                     })
             };
+            this.linkEditor.appendChild(this.linkDrag);
             this.linkEditor.appendChild(this.linkText);
             this.linkEditor.appendChild(linkBtn);
-            this.linkEditor.appendChild(linkDrag)
             this.tooltip.appendChild(this.linkEditor);
         }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e3eb034fa..85ddadd64 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,7 +5,6 @@ import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import './DocumentDecorations.scss';
-import { MainOverlayTextBox } from "./MainOverlayTextBox";
 import { DocumentView, PositionDocument } from "./nodes/DocumentView";
 import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
@@ -25,11 +24,9 @@ import { faLink } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
-import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionView } from "./collections/CollectionView";
-import { createCipher } from "crypto";
-import { FieldView } from "./nodes/FieldView";
 import { DocumentManager } from "../util/DocumentManager";
+import { FormattedTextBox } from "./nodes/FormattedTextBox";
 
 library.add(faLink);
 
@@ -328,6 +325,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             let selDoc = SelectionManager.SelectedDocuments()[0];
             let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined;
             let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
+            FormattedTextBox.InputBoxOverlay = undefined;
             DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
                     dragComplete: action(emptyFunction),
@@ -410,7 +408,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        MainOverlayTextBox.Instance.SetTextDoc();
+        FormattedTextBox.InputBoxOverlay = undefined;
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 3b75c248a..91f626737 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -1,16 +1,12 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import "normalize.css";
 import * as React from 'react';
 import { emptyFunction, returnTrue, returnZero } from '../../Utils';
-import '../northstar/model/ModelExtensions';
-import '../northstar/utils/Extensions';
 import { DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
+import "normalize.css";
 import "./MainOverlayTextBox.scss";
 import { FormattedTextBox } from './nodes/FormattedTextBox';
-import { Doc } from '../../new_fields/Doc';
-import { NumCast } from '../../new_fields/Types';
 
 interface MainOverlayTextBoxProps {
 }
@@ -18,8 +14,6 @@ interface MainOverlayTextBoxProps {
 @observer
 export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
     public static Instance: MainOverlayTextBox;
-    @observable public TextDoc?: Doc = undefined;
-    public TextScroll: number = 0;
     @observable _textXf: () => Transform = () => Transform.Identity();
     private _textFieldKey: string = "data";
     private _textColor: string | null = null;
@@ -30,15 +24,18 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         super(props);
         this._textProxyDiv = React.createRef();
         MainOverlayTextBox.Instance = this;
+        reaction(() => FormattedTextBox.InputBoxOverlay,
+            (box?: FormattedTextBox) => {
+                if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform);
+                else this.setTextDoc();
+            });
     }
 
     @action
-    SetTextDoc(textDoc?: Doc, textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
+    private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
         if (this._textTargetDiv) {
             this._textTargetDiv.style.color = this._textColor;
         }
-
-        this.TextDoc = textDoc;
         this._textFieldKey = textFieldKey!;
         this._textXf = tx ? tx : () => Transform.Identity();
         this._textTargetDiv = div;
@@ -46,15 +43,13 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity();
             this._textColor = div.style.color;
             div.style.color = "transparent";
-            this.TextScroll = div.scrollTop;
         }
     }
 
     @action
     textScroll = (e: React.UIEvent) => {
         if (this._textProxyDiv.current && this._textTargetDiv) {
-            this.TextScroll = (e as any)._targetInst.stateNode.scrollTop;//  this._textProxyDiv.current.children[0].scrollTop;
-            this._textTargetDiv.scrollTop = this.TextScroll;
+            this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop;
         }
     }
 
@@ -64,11 +59,12 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             document.addEventListener('pointerup', this.textBoxUp);
         }
     }
+    @action
     textBoxMove = (e: PointerEvent) => {
         if (e.movementX > 1 || e.movementY > 1) {
             document.removeEventListener("pointermove", this.textBoxMove);
             document.removeEventListener('pointerup', this.textBoxUp);
-            let dragData = new DragManager.DocumentDragData([this.TextDoc!]);
+            let dragData = new DragManager.DocumentDragData(FormattedTextBox.InputBoxOverlay ? [FormattedTextBox.InputBoxOverlay.props.Document] : []);
             const [left, top] = this._textXf().inverse().transformPoint(0, 0);
             dragData.xOffset = e.clientX - left;
             dragData.yOffset = e.clientY - top;
@@ -86,13 +82,13 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     }
 
     render() {
-        if (this.TextDoc && this._textTargetDiv) {
+        if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
             let textRect = this._textTargetDiv.getBoundingClientRect();
             let s = this._textXf().Scale;
             return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
-                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                         ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} />
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 16818affd..6dd0e5935 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -322,8 +322,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     render() {
         library.add(faCog);
         library.add(faPlus);
-        //This can't just pass FieldValue to filter because filter passes other arguments to the passed in function, which end up as default values in FieldValue
-        const children = (this.children || []).filter(doc => FieldValue(doc));
+        const children = this.children;
         return (
             <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
                 onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 828ac880a..082692d8d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -50,7 +50,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get children() {
             //TODO tfs: This might not be what we want?
             //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc));
+            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc)).map(doc => doc as Doc);
         }
 
         @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 797f94d5f..c1d149098 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -70,7 +70,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     public getActiveDocuments = () => {
         const curPage = FieldValue(this.Document.curPage, -1);
-        return FieldValue(this.children, [] as Doc[]).filter(doc => {
+        return this.children.filter(doc => {
             var page = NumCast(doc.page, -1);
             return page === curPage || page === -1;
         });
@@ -169,7 +169,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // if (!this.props.active()) {
         //     return;
         // }
-        let childSelected = (this.children || []).filter(doc => doc).some(doc => {
+        let childSelected = this.children.some(doc => {
             var dv = DocumentManager.Instance.getDocumentView(doc);
             return dv && SelectionManager.IsSelected(dv) ? true : false;
         });
@@ -222,7 +222,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     bringToFront = (doc: Doc) => {
-        const docs = (this.children || []);
+        const docs = this.children;
         docs.slice().sort((doc1, doc2) => {
             if (doc1 === doc) return 1;
             if (doc2 === doc) return -1;
@@ -268,7 +268,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed.struct
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
-        let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => {
+        let docviews = this.children.reduce((prev, doc) => {
             if (!(doc instanceof Doc)) return prev;
             var page = NumCast(doc.page, -1);
             if (page === curPage || page === -1) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 613c24fa4..cdc1bdc85 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { observer } from "mobx-react";
-import { computed } from "mobx";
+import { computed, observable } from "mobx";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { VideoBox } from "./VideoBox";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 445a834ee..e7fb94777 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,29 +1,31 @@
-import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx";
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { EditorState, Plugin, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
+import { Doc, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { DocServer } from "../../DocServer";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorKeymap";
 import { inpRules } from "../../util/RichTextRules";
-import { schema, ImageResizeView } from "../../util/RichTextSchema";
+import { ImageResizeView, schema } from "../../util/RichTextSchema";
+import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { ContextMenu } from "../../views/ContextMenu";
-import { MainOverlayTextBox } from "../MainOverlayTextBox";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Opt, Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
-import { observer } from "mobx-react";
-import { InkingControl } from "../InkingControl";
-import { StrCast, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { Id } from "../../../new_fields/RefField";
-import { UndoManager } from "../../util/UndoManager";
 
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
@@ -62,15 +64,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _proseRef: React.RefObject<HTMLDivElement>;
     private _editorView: Opt<EditorView>;
     private _gotDown: boolean = false;
+    private _dropDisposer?: DragManager.DragDropDisposer;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _inputReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
+    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
+    @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
+    public static InputBoxOverlayScroll: number = 0;
 
     constructor(props: FieldViewProps) {
         super(props);
 
         this._ref = React.createRef();
         this._proseRef = React.createRef();
+        if (this.props.isOverlay) {
+            DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
+        }
     }
 
     _applyingChange: boolean = false;
@@ -94,7 +104,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @undoBatch
+    @action
+    drop = async (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.LinkDragData) {
+            let sourceDoc = de.data.linkSourceDocument;
+            let destDoc = this.props.Document;
+
+            const protoDest = destDoc.proto;
+            const protoSrc = sourceDoc.proto;
+            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            de.data.droppedDocuments.push(destDoc);
+            e.stopPropagation();
+        }
+    }
+
     componentDidMount() {
+        if (this._ref.current) {
+            this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, {
+                handlers: { drop: this.drop.bind(this) }
+            });
+        }
         const config = {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
@@ -117,7 +147,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         };
 
         if (this.props.isOverlay) {
-            this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc[Id],
+            this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay,
                 () => {
                     if (this._editorView) {
                         this._editorView.destroy();
@@ -127,7 +157,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             );
         } else {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
-                () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
+                () => {
+                    if (this.props.isSelected()) {
+                        FormattedTextBox.InputBoxOverlay = this;
+                        FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop;
+                    }
+                });
         }
 
 
@@ -178,6 +213,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this._proxyReactionDisposer) {
             this._proxyReactionDisposer();
         }
+        if (this._dropDisposer) {
+            this._dropDisposer();
+        }
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
@@ -186,6 +224,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
         }
+        if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey) {
+            if (e.target && (e.target as any).href) {
+                let href = (e.target as any).href;
+                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+                    let docid = href.replace(DocServer.prepend("/doc/"), "");
+                    DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                        if (f instanceof Doc) {
+                            if (DocumentManager.Instance.getDocumentView(f))
+                                DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                            else CollectionDockingView.Instance.AddRightSplit(f);
+                        }
+                    }));
+                }
+                e.stopPropagation();
+                e.preventDefault();
+            }
+
+        }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             this._gotDown = true;
             e.preventDefault();
@@ -199,12 +255,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @action
     onFocused = (e: React.FocusEvent): void => {
         if (!this.props.isOverlay) {
-            MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
+            FormattedTextBox.InputBoxOverlay = this;
         } else {
             if (this._proseRef.current) {
-                this._proseRef.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
+                this._proseRef.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From b29a0d1cef60b55f609fcd94ad38232ae557e681 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 16 May 2019 18:32:57 -0400
Subject: Fixed linter errors

---
 src/client/goldenLayout.d.ts                       |  3 +++
 src/client/northstar/dash-fields/HistogramField.ts |  2 +-
 src/client/northstar/model/ModelHelpers.ts         |  9 +++----
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/RichTextSchema.tsx                 | 16 +++++------
 src/client/util/TooltipTextMenu.tsx                | 13 ++++-----
 src/client/views/DocumentDecorations.tsx           | 31 +++++++++++-----------
 src/client/views/PresentationView.tsx              |  4 +--
 src/client/views/TemplateMenu.tsx                  |  4 +--
 .../views/collections/CollectionDockingView.tsx    |  5 ++--
 .../views/collections/CollectionSchemaView.tsx     | 10 +++----
 .../views/collections/CollectionTreeView.tsx       |  9 ++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 13 ++++-----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  8 +++---
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/KeyValuePair.tsx            |  3 ++-
 src/client/views/nodes/VideoBox.tsx                | 22 +++++++--------
 src/new_fields/CursorField.ts                      |  6 ++---
 src/new_fields/Doc.ts                              |  4 +--
 src/new_fields/ObjectField.ts                      |  2 +-
 .../authentication/models/current_user_utils.ts    |  2 +-
 24 files changed, 91 insertions(+), 85 deletions(-)
 create mode 100644 src/client/goldenLayout.d.ts

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/goldenLayout.d.ts b/src/client/goldenLayout.d.ts
new file mode 100644
index 000000000..b50240563
--- /dev/null
+++ b/src/client/goldenLayout.d.ts
@@ -0,0 +1,3 @@
+
+declare const GoldenLayout: any;
+export = GoldenLayout;
\ No newline at end of file
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index aabc77bb2..1ee2189b9 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -52,7 +52,7 @@ export class HistogramField extends ObjectField {
 
     [Copy]() {
         let y = this.HistoOp;
-        let z = this.HistoOp["Copy"];
+        let z = this.HistoOp.Copy;
         return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index 80bb71224..88e6e72b8 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -32,12 +32,9 @@ export class ModelHelpers {
 
     public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
         return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => {
-            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters)
-                return true;
-            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters)
-                return true;
-            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters)
-                return true;
+            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters) return true;
+            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters) return true;
+            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters) return true;
             return false;
         });
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 26da34e67..05eb5c38a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -41,7 +41,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
     let srcTarg = sourceDoc.proto;
     let draggedDocs: Doc[] = [];
-    let draggedFromDocs: Doc[] = []
+    let draggedFromDocs: Doc[] = [];
     if (srcTarg) {
         let linkToDocs = await DocListCastAsync(srcTarg.linkedToDocs);
         let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c0e6f7899..3e3e98206 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -97,13 +97,13 @@ export const nodes: { [index: string]: NodeSpec } = {
                     title: dom.getAttribute("title"),
                     alt: dom.getAttribute("alt"),
                     width: Math.min(100, Number(dom.getAttribute("width"))),
-                }
+                };
             }
         }],
         // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why?
         toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` }
-            return ["img", { ...node.attrs, ...attrs }]
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
         }
     },
 
@@ -375,7 +375,7 @@ export class ImageResizeView {
                 const currentX = e.pageX;
                 const diffInPx = currentX - startX;
                 self._outer.style.width = `${startWidth + diffInPx}`;
-            }
+            };
 
             const onpointerup = () => {
                 document.removeEventListener("pointermove", onpointermove);
@@ -384,11 +384,11 @@ export class ImageResizeView {
                     view.state.tr.setNodeMarkup(getPos(), null,
                         { src: node.attrs.src, width: self._outer.style.width })
                         .setSelection(view.state.selection));
-            }
+            };
 
-            document.addEventListener("pointermove", onpointermove)
-            document.addEventListener("pointerup", onpointerup)
-        }
+            document.addEventListener("pointermove", onpointermove);
+            document.addEventListener("pointerup", onpointerup);
+        };
 
         this._outer.appendChild(this._handle);
         this._outer.appendChild(this._img);
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 6eb654319..223921428 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -175,7 +175,7 @@ export class TooltipTextMenu {
             this.linkText.style.width = "150px";
             this.linkText.style.overflow = "hidden";
             this.linkText.style.color = "white";
-            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
+            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
             linkBtn.style.width = "20px";
@@ -192,8 +192,9 @@ export class TooltipTextMenu {
                         let docid = href.replace(DocServer.prepend("/doc/"), "");
                         DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                             if (f instanceof Doc) {
-                                if (DocumentManager.Instance.getDocumentView(f))
+                                if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                                }
                                 else CollectionDockingView.Instance.AddRightSplit(f);
                             }
                         }));
@@ -201,7 +202,7 @@ export class TooltipTextMenu {
                     e.stopPropagation();
                     e.preventDefault();
                 }
-            }
+            };
             this.linkDrag = document.createElement("img");
             this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
             this.linkDrag.style.width = "20px";
@@ -216,12 +217,12 @@ export class TooltipTextMenu {
                     {
                         handlers: {
                             dragComplete: action(() => {
-                                let m = dragData.droppedDocuments as Doc[];
+                                let m = dragData.droppedDocuments;
                                 this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
                             }),
                         },
                         hideSource: false
-                    })
+                    });
             };
             this.linkEditor.appendChild(this.linkDrag);
             this.linkEditor.appendChild(this.linkText);
@@ -239,7 +240,7 @@ export class TooltipTextMenu {
                 e.stopPropagation();
                 e.preventDefault();
             }
-        }
+        };
         this.tooltip.appendChild(this.linkEditor);
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b2c65a31f..7083b1003 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -250,22 +250,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
             }
             if (!this._removeIcon) {
-                if (selectedDocs.length === 1)
+                if (selectedDocs.length === 1) {
                     this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
-                else
-                    if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
-                        Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
-                        let docViews = SelectionManager.ViewsSortedVertically();
-                        let topDocView = docViews[0];
-                        let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                        if (ind !== -1) {
-                            topDocView.templates.splice(ind, 1);
-                            topDocView.props.Document.subBulletDocs = undefined;
-                        } else {
-                            topDocView.addTemplate(Templates.Bullet);
-                            topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                        }
+                } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+                    Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
+                    let docViews = SelectionManager.ViewsSortedVertically();
+                    let topDocView = docViews[0];
+                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                    if (ind !== -1) {
+                        topDocView.templates.splice(ind, 1);
+                        topDocView.props.Document.subBulletDocs = undefined;
+                    } else {
+                        topDocView.addTemplate(Templates.Bullet);
+                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
                     }
+                }
             }
             this._removeIcon = false;
         }
@@ -537,9 +536,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         if (temp !== Templates.Bullet.Layout || i === 0) {
                             res.push(temp);
                         }
-                    })
+                    });
                 }
-                return res
+                return res;
             }, [] as string[]);
             let checked = false;
             docTemps.forEach(temp => {
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 098e725c7..7d0dc2913 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,7 +1,7 @@
 import { observer } from "mobx-react";
-import React = require("react")
+import React = require("react");
 import { observable, action, runInAction, reaction } from "mobx";
-import "./PresentationView.scss"
+import "./PresentationView.scss";
 import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index cfe1b0663..e5b679e24 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -43,7 +43,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
-            if (template.Name == "Bullet") {
+            if (template.Name === "Bullet") {
                 let topDocView = this.props.docs[0];
                 topDocView.addTemplate(template);
                 topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
@@ -52,7 +52,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             }
             this.props.templates.set(template, true);
         } else {
-            if (template.Name == "Bullet") {
+            if (template.Name === "Bullet") {
                 let topDocView = this.props.docs[0];
                 topDocView.removeTemplate(template);
                 topDocView.props.Document.subBulletDocs = undefined;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 58f1e33a1..deec64225 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -83,7 +83,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     child.contentItems[0].remove();
                     this.layoutChanged(document);
                     return true;
-                } else
+                } else {
                     Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
                         if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
                             child.contentItems[j].remove();
@@ -94,8 +94,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         return false;
                     });
+                }
                 return false;
-            })
+            });
         }
         if (retVal) {
             this.stateChanged();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f4ad5b357..497dfde88 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -90,7 +90,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         let fieldContentView = <FieldView {...props} />;
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = (e: React.PointerEvent) =>
-            (this.props.CollectionView!.props.isSelected() ?
+            (this.props.CollectionView.props.isSelected() ?
                 SetupDrag(reference, () => props.Document, this.props.moveDocument)(e) : undefined);
         let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
             const res = run({ this: doc });
@@ -127,7 +127,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                         }
                         const run = script.run;
                         //TODO This should be able to be refactored to compile the script once
-                        const val = await DocListCastAsync(this.props.Document[this.props.fieldKey])
+                        const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
                         val && val.forEach(doc => applyToDoc(doc, run));
                     }}>
                 </EditableView>
@@ -230,14 +230,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         DocListCast(this.props.Document.data).map(doc => {
             csv += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "") + ",", "");
             csv = csv.substr(0, csv.length - 1) + "\n";
-        })
+        });
         csv.substring(0, csv.length - 1);
         let dbName = StrCast(this.props.Document.title);
         let res = await Gateway.Instance.PostSchema(csv, dbName);
         if (self.props.CollectionView.props.addDocument) {
             let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName });
             if (schemaDoc) {
-                self.props.CollectionView.props.addDocument(schemaDoc, false); 
+                self.props.CollectionView.props.addDocument(schemaDoc, false);
             }
         }
     }
@@ -263,7 +263,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get previewDocument(): Doc | undefined {
         const children = DocListCast(this.props.Document[this.props.fieldKey]);
         const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
-        return selected ? (this.previewScript && this.previewScript != "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+        return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
     }
     get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
     get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70c09d97c..6acef434e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -56,7 +56,7 @@ class TreeView extends React.Component<TreeViewProps> {
         } else {
             CollectionDockingView.Instance.AddRightSplit(this.props.document);
         }
-    };
+    }
 
     get children() {
         return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
@@ -184,8 +184,9 @@ class TreeView extends React.Component<TreeViewProps> {
                             {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
                         </div>
                     </ul >);
-                } else
+                } else {
                     bulletType = BulletType.Collapsed;
+                }
             }
         });
         return <div className="treeViewItem-container"
@@ -198,8 +199,8 @@ class TreeView extends React.Component<TreeViewProps> {
         </div>;
     }
     public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => child instanceof Doc && !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
-            <TreeView document={child as Doc} key={(child as Doc)[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+        return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 12edb2c2a..c3c4115b8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -63,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             e.preventDefault();
             (async () => {
                 let text: string = await navigator.clipboard.readText();
-                let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
+                let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 for (let i = 0; i < ns.length - 1; i++) {
                     while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
                         ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
@@ -80,7 +80,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
                     this.props.addDocument(newBox, false);
                     y += 40 * this.props.getTransform().Scale;
-                })
+                });
             })();
         } else if (e.key === "b" && e.ctrlKey) {
             //heuristically converts pasted text into a table.
@@ -93,9 +93,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             e.preventDefault();
             (async () => {
                 let text: string = await navigator.clipboard.readText();
-                let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
-                while (ns.length > 0 && ns[0].split("\t").length < 2)
+                let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+                while (ns.length > 0 && ns[0].split("\t").length < 2) {
                     ns.splice(0, 1);
+                }
                 if (ns.length > 0) {
                     let columns = ns[0].split("\t");
                     let docList: Doc[] = [];
@@ -109,7 +110,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                         let doc = new Doc();
                         columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
                         if (groupAttr) {
-                            doc["_group"] = groupAttr;
+                            doc._group = groupAttr;
                         }
                         doc.title = i.toString();
                         docList.push(doc);
@@ -284,7 +285,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     //     summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]);
                     // });
                     this.props.addLiveTextDocument(summary);
-                })
+                });
             }
             else {
                 this.props.addDocument(newCollection, false);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7c7ca9e25..d4f660b3f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -149,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
                         maximizedDoc.willMaximize = isMinimized;
                         maximizedDoc.isMinimized = false;
-                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0])
+                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
                     }
                 }
             });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cf8bcbb42..428dd9b36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -244,7 +244,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
-            if (de.mods == "Control") {
+            if (de.mods === "Control") {
                 let src = protoSrc ? protoSrc : sourceDoc;
                 let dst = protoDest ? protoDest : destDoc;
                 dst.data = src;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f24d4ae88..f30022508 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos) }
+                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
                 }
             });
             let text = StrCast(this.props.Document.documentText);
@@ -232,9 +232,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                         if (f instanceof Doc) {
-                            if (DocumentManager.Instance.getDocumentView(f))
+                            if (DocumentManager.Instance.getDocumentView(f)) {
                                 DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
-                            else CollectionDockingView.Instance.AddRightSplit(f);
+                            } else {
+                                CollectionDockingView.Instance.AddRightSplit(f);
+                            }
                         }
                     }));
                 }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6472ae711..3cc60a6c5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,7 +165,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
         let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50);
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
-        let id = this.props.id; // bcz: used to set id = "isExpander" in templates.tsx
+        let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
         return (
             <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index c9d665ceb..86437a6c1 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document }
+    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
 
     constructor(props: FieldViewProps) {
         super(props);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 5de660d57..4f7919f50 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,8 +46,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button className="keyValuePair-td-key-delete" onClick={() => {
-                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1)
+                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
                                 props.Document[props.fieldKey] = undefined;
+                            }
                             else props.Document.proto![props.fieldKey] = undefined;
                         }}>
                             X
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 96dc884c8..81c429a02 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -70,7 +70,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action
-    updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime);
+    updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime)
 
     componentDidMount() {
         if (this.props.setVideoBox) this.props.setVideoBox(this);
@@ -106,31 +106,31 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                     "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0',
                 },
 
-            }
+            };
             try {
                 let responseSchema: any = {};
-                const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig)
+                const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig);
                 const dataHtml = videoInfoResponse;
                 const start = dataHtml.indexOf('ytplayer.config = ') + 18;
                 const end = dataHtml.indexOf(';ytplayer.load');
-                const subString = dataHtml.substring(start, end)
+                const subString = dataHtml.substring(start, end);
                 const subJson = JSON.parse(subString);
                 const stringSub = subJson.args.player_response;
                 const stringSubJson = JSON.parse(stringSub);
                 const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats;
-                const videoDetails = stringSubJson.videoDetails
-                responseSchema["adaptiveFormats"] = adaptiveFormats;
-                responseSchema["videoDetails"] = videoDetails;
-                resolve(responseSchema)
+                const videoDetails = stringSubJson.videoDetails;
+                responseSchema.adaptiveFormats = adaptiveFormats;
+                responseSchema.videoDetails = videoDetails;
+                resolve(responseSchema);
             }
             catch (err) {
                 console.log(`
                 --- Youtube ---
                 Function: getMp4ForVideo
-                Error: `, err)
-                reject(err)
+                Error: `, err);
+                reject(err);
             }
-        })
+        });
     }
     onPointerDown = (e: React.PointerEvent) => {
         e.preventDefault();
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
index 7fd326a5f..fc144222c 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/new_fields/CursorField.ts
@@ -6,17 +6,17 @@ import { serializable, createSimpleSchema, object } from "serializr";
 export type CursorPosition = {
     x: number,
     y: number
-}
+};
 
 export type CursorMetadata = {
     id: string,
     identifier: string
-}
+};
 
 export type CursorData = {
     metadata: CursorMetadata,
     position: CursorPosition
-}
+};
 
 const PositionSchema = createSimpleSchema({
     x: true,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index de72be3ea..c898f697b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -42,8 +42,8 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
 
-export function DocListCast(field: FieldResult) {
-    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc);
+export function DocListCast(field: FieldResult): Doc[] {
+    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc) as Doc[];
 }
 
 @Deserializable("doc").withFields(["id"])
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index f276bfa67..51768c6db 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -6,7 +6,7 @@ export const Parent = Symbol("Parent");
 export const Copy = Symbol("Copy");
 
 export abstract class ObjectField {
-    protected [OnUpdate](diff?: any) { };
+    protected [OnUpdate](diff?: any) { }
     private [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
 }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5b63ac356..aef2d3f4a 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -66,7 +66,7 @@ export class CurrentUserUtils {
             NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
             await Gateway.Instance.ClearCatalog();
             const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-            let extras = await Promise.all(extraSchemas.map(async sc => await Gateway.Instance.GetSchema("", sc)));
+            let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
             let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
             if (catprom) await Promise.all(catprom);
         } catch (e) {
-- 
cgit v1.2.3-70-g09d2


From 6e65b1e32eb972358d9c8dbd7f8d984d3baefc4b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 16 May 2019 22:56:19 -0400
Subject: fixed links so that you can adjust the pan location of a collection
 when its a link endoint

---
 src/client/util/DocumentManager.ts                        |  5 +++--
 src/client/util/DragManager.ts                            |  2 +-
 src/client/util/TooltipTextMenu.tsx                       |  1 -
 src/client/views/Main.tsx                                 |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx |  3 ++-
 src/client/views/nodes/DocumentView.tsx                   | 11 ++++++-----
 src/client/views/nodes/FormattedTextBox.tsx               |  4 +---
 src/client/views/nodes/LinkBox.tsx                        |  2 +-
 src/client/views/nodes/LinkMenu.tsx                       |  5 ++---
 src/new_fields/Doc.ts                                     | 10 ++++++----
 10 files changed, 23 insertions(+), 22 deletions(-)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 51f5fbe9f..c6962ad8d 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -110,7 +110,8 @@ export class DocumentManager {
     }
 
     @undoBatch
-    public jumpToDocument = async (doc: Doc): Promise<void> => {
+    public jumpToDocument = async (docDelegate: Doc, makeCopy: boolean = true): Promise<void> => {
+        let doc = docDelegate.proto ? docDelegate.proto : docDelegate;
         const page = NumCast(doc.page, undefined);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
@@ -122,7 +123,7 @@ export class DocumentManager {
             docView.props.focus(docView.props.Document);
         } else {
             if (!contextDoc) {
-                CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(doc));
+                CollectionDockingView.Instance.AddRightSplit(docDelegate ? (makeCopy ? Doc.MakeCopy(docDelegate) : docDelegate) : Doc.MakeDelegate(doc));
             } else {
                 let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
                 if (contextView) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 05eb5c38a..7f75a95f0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -336,7 +336,7 @@ export namespace DragManager {
                         x: e.x,
                         y: e.y,
                         data: dragData,
-                        mods: e.ctrlKey ? "Control" : ""
+                        mods: e.altKey ? "AltKey" : ""
                     }
                 })
             );
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 223921428..4d40d09b2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -186,7 +186,6 @@ export class TooltipTextMenu {
                 let node = this.view.state.selection.$from.nodeAfter;
                 let link = node && node.marks.find(m => m.type.name === "link");
                 if (link) {
-                    console.log("Link to : " + link.attrs.href);
                     let href: string = link.attrs.href;
                     if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
                         let docid = href.replace(DocServer.prepend("/doc/"), "");
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 866da010a..51ad0aa49 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -244,7 +244,7 @@ export class Main extends React.Component {
         let logoutRef = React.createRef<HTMLDivElement>();
 
         return [
-            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.ctrlKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
             <div id="toolbar" key="toolbar">
                 <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
                 <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d4f660b3f..001ce3095 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -205,11 +205,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     }
                 }
                 else if (linkedToDocs.length || linkedFromDocs.length) {
+                    SelectionManager.DeselectAll();
                     let linkedFwdDocs = [
                         linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
                         linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
                     if (linkedFwdDocs) {
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40ad55d25..5264773b0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -27,6 +27,7 @@ import { DocServer } from "../../DocServer";
 import { Id } from "../../../new_fields/RefField";
 import { PresentationView } from "../PresentationView";
 import { SearchUtil } from "../../util/SearchUtil";
+import { ObjectField, Copy } from "../../../new_fields/ObjectField";
 
 const linkSchema = createSchema({
     title: "string",
@@ -245,17 +246,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
 
-            const protoDest = destDoc.proto;
-            const protoSrc = sourceDoc.proto;
-            if (de.mods === "Control") {
+            if (de.mods === "AltKey") {
+                const protoDest = destDoc.proto;
+                const protoSrc = sourceDoc.proto;
                 let src = protoSrc ? protoSrc : sourceDoc;
                 let dst = protoDest ? protoDest : destDoc;
-                dst.data = src;
+                dst.data = (src.data! as ObjectField)[Copy]();
                 dst.nativeWidth = src.nativeWidth;
                 dst.nativeHeight = src.nativeHeight;
             }
             else {
-                Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+                Doc.MakeLink(sourceDoc, destDoc);
                 de.data.droppedDocuments.push(destDoc);
             }
             e.stopPropagation();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f30022508..bf98fb40b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -111,9 +111,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
 
-            const protoDest = destDoc.proto;
-            const protoSrc = sourceDoc.proto;
-            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            Doc.MakeLink(sourceDoc, destDoc);
             de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 611cb66b6..68b692aad 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -31,7 +31,7 @@ export class LinkBox extends React.Component<Props> {
     @undoBatch
     onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
         e.stopPropagation();
-        DocumentManager.Instance.jumpToDocument(this.props.pairedDoc);
+        DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey);
     }
 
     onEditButtonPressed = (e: React.PointerEvent): void => {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 11117122d..4cf798249 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -6,8 +6,7 @@ import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Cast, FieldValue } from "../../../new_fields/Types";
-import { listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/RefField";
 
 interface Props {
@@ -24,7 +23,7 @@ export class LinkMenu extends React.Component<Props> {
         return links.map(link => {
             let doc = FieldValue(Cast(link[key], Doc));
             if (doc) {
-                return <LinkBox key={doc[Id]} linkDoc={link} linkName={Cast(link.title, "string", "")} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
+                return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
             }
         });
     }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c898f697b..ad3b880cd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -216,6 +216,8 @@ export namespace Doc {
     }
 
     export function MakeLink(source: Doc, target: Doc) {
+        let protoSrc = source.proto ? source.proto : source;
+        let protoTarg = target.proto ? target.proto : target;
         UndoManager.RunInBatch(() => {
             let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
             //let linkDoc = new Doc;
@@ -226,15 +228,15 @@ export namespace Doc {
             linkDoc.proto!.linkedTo = target;
             linkDoc.proto!.linkedFrom = source;
 
-            let linkedFrom = Cast(target.linkedFromDocs, listSpec(Doc));
+            let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
             if (!linkedFrom) {
-                target.linkedFromDocs = linkedFrom = new List<Doc>();
+                protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
             }
             linkedFrom.push(linkDoc);
 
-            let linkedTo = Cast(source.linkedToDocs, listSpec(Doc));
+            let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
             if (!linkedTo) {
-                source.linkedToDocs = linkedTo = new List<Doc>();
+                protoSrc.linkedToDocs = linkedTo = new List<Doc>();
             }
             linkedTo.push(linkDoc);
             return linkDoc;
-- 
cgit v1.2.3-70-g09d2


From af2346983eae1145167b70faf96a9aec0ca82427 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 20 May 2019 00:09:47 -0400
Subject: Fixed a bunch of demo bugs Moved Field Symbols to separate file
 Editing is mostly working in debug viewer

---
 deploy/debug/viewer.html                           |   1 +
 src/client/DocServer.ts                            |   3 +-
 src/client/northstar/dash-fields/HistogramField.ts |   7 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |   2 +-
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/SearchUtil.ts                      |   2 +-
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/util/type_decls.d                       | 128 +++++++--------------
 src/client/views/EditableView.tsx                  |  10 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PresentationView.tsx              |   2 +-
 src/client/views/SearchBox.tsx                     |   2 +-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  14 ++-
 src/client/views/collections/CollectionPDFView.tsx |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/collections/CollectionVideoView.tsx      |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/ParentDocumentSelector.tsx   |   2 +-
 .../CollectionFreeFormLinksView.tsx                |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   3 -
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/KeyValueBox.tsx             |   4 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/debug/Viewer.tsx                               |  53 +++++++--
 src/new_fields/CursorField.ts                      |   7 +-
 src/new_fields/DateField.ts                        |   7 +-
 src/new_fields/Doc.ts                              |  43 ++++---
 src/new_fields/FieldSymbols.ts                     |  10 ++
 src/new_fields/HtmlField.ts                        |   7 +-
 src/new_fields/IconField.ts                        |   7 +-
 src/new_fields/InkField.ts                         |   7 +-
 src/new_fields/List.ts                             |  16 ++-
 src/new_fields/ObjectField.ts                      |   7 +-
 src/new_fields/Proxy.ts                            |   9 +-
 src/new_fields/RefField.ts                         |   5 +-
 src/new_fields/RichTextField.ts                    |   7 +-
 src/new_fields/URLField.ts                         |   9 +-
 src/new_fields/util.ts                             |   7 +-
 42 files changed, 240 insertions(+), 173 deletions(-)
 create mode 100644 src/new_fields/FieldSymbols.ts

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/deploy/debug/viewer.html b/deploy/debug/viewer.html
index 3785a6602..8c265ccb8 100644
--- a/deploy/debug/viewer.html
+++ b/deploy/debug/viewer.html
@@ -3,6 +3,7 @@
 <head>
     <title>Document Debugger</title>
     <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
 </head>
 
 <body>
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index f1b50d5a0..cbcf751ee 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -3,7 +3,8 @@ import { MessageStore } from "./../server/Message";
 import { Opt } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
-import { RefField, HandleUpdate, Id } from '../new_fields/RefField';
+import { RefField } from '../new_fields/RefField';
+import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
 
 export namespace DocServer {
     const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 1ee2189b9..31040a474 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -3,10 +3,11 @@ import { custom, serializable } from "serializr";
 import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
 import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { ObjectField, Copy } from "../../../new_fields/ObjectField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { OmitKeys } from "../../../Utils";
 import { Deserializable } from "../../util/SerializationHelper";
+import { Copy, ToScriptString } from "../../../new_fields/FieldSymbols";
 
 function serialize(field: HistogramField) {
     let obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
@@ -55,4 +56,8 @@ export class HistogramField extends ObjectField {
         let z = this.HistoOp.Copy;
         return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index eb1ad69b7..a9646ed31 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -19,7 +19,7 @@ import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 import { Cast } from "../../../new_fields/Types";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index be448098a..a5e768dcf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,10 +5,10 @@ import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { undoBatch } from './UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../new_fields/RefField';
 import { CollectionView } from '../views/collections/CollectionView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
+import { Id } from '../../new_fields/FieldSymbols';
 
 
 export class DocumentManager {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 4ccff0d1b..e8eb70837 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,7 +1,7 @@
 import * as rp from 'request-promise';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 
 export namespace SearchUtil {
     export function Search(query: string, returnDocs: true): Promise<Doc[]>;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 4d40d09b2..a1f80120f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -22,12 +22,12 @@ import { throwStatement } from "babel-types";
 import { View } from "@react-pdf/renderer";
 import { DragManager } from "./DragManager";
 import { Doc, Opt, Field } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/RefField";
 import { Utils } from "../northstar/utils/Utils";
 import { DocServer } from "../DocServer";
 import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DocumentManager } from "./DocumentManager";
+import { Id } from "../../new_fields/FieldSymbols";
 
 const SVG = "http://www.w3.org/2000/svg";
 
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 47c3481b2..51114d0e2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -119,104 +119,54 @@ interface URL {
     username: string;
     toJSON(): string;
 }
+interface PromiseLike<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
+}
+interface Promise<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
+    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
+}
 
-declare type FieldId = string;
+declare const Update: unique symbol;
+declare const Self: unique symbol;
+declare const SelfProxy: unique symbol;
+declare const HandleUpdate: unique symbol;
+declare const Id: unique symbol;
+declare const OnUpdate: unique symbol;
+declare const Parent: unique symbol;
+declare const Copy: unique symbol;
+declare const ToScriptString: unique symbol;
 
-declare abstract class Field {
-    Id: FieldId;
-    abstract ToScriptString(): string;
-    abstract TrySetValue(value: any): boolean;
-    abstract GetValue(): any;
-    abstract Copy(): Field;
-}
+declare abstract class RefField {
+    readonly [Id]: FieldId;
+
+    constructor(id?: FieldId);
+    protected [HandleUpdate]?(diff: any): void;
 
-declare abstract class BasicField<T> extends Field {
-    constructor(data: T);
-    Data: T;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
+    abstract [ToScriptString](): string;
 }
 
-declare class TextField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ImageField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class HtmlField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class NumberField extends BasicField<number>{
-    constructor();
-    constructor(data: number);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class WebField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ListField<T> extends BasicField<T[]>{
-    constructor();
-    constructor(data: T[]);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class Key extends Field {
-    constructor(name:string);
-    Name: string;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
-}
-declare type FIELD_WAITING = null;
-declare type Opt<T> = T | undefined;
-declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
-// @ts-ignore
-declare class Document extends Field {
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
+declare abstract class ObjectField {
+    protected [OnUpdate](diff?: any): void;
+    private [Parent]?: RefField | ObjectField;
+    abstract [Copy](): ObjectField;
 
-    Width(): number;
-    Height(): number;
-    Scale(): number;
-    Title: string;
+    abstract [ToScriptString](): string;
+}
+declare type FieldId = string;
 
-    Get(key: Key): FieldValue<Field>;
-    GetAsync(key: Key, callback: (field: Field) => void): boolean;
-    GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void;
-    GetT<T extends Field>(key: Key, ctor: { new(): T }): FieldValue<T>;
-    GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }): T;
-    GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T;
-    GetHtml(key: Key, defaultVal: string): string;
-    GetNumber(key: Key, defaultVal: number): number;
-    GetText(key: Key, defaultVal: string): string;
-    GetList<T extends Field>(key: Key, defaultVal: T[]): T[];
-    Set(key: Key, field: Field | undefined): void;
-    SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }): void;
-    SetText(key: Key, value: string): void;
-    SetNumber(key: Key, value: number): void;
-    GetPrototype(): FieldValue<Document>;
-    GetAllPrototypes(): Document[];
-    MakeDelegate(): Document;
+declare type Field = number | string | boolean | ObjectField | RefField;
+
+declare type Opt<T> = T | undefined;
+declare class Doc extends RefField {
+    [key: string]: Field | undefined;
+    [ToScriptString](): string;
 }
 
-declare const KeyStore: {
-    [name: string]: Key;
+declare class ListImpl<T extends Field> extends ObjectField {
+    [index: number]: T | (T extends RefField ? Promise<T> : never);
+    [ToScriptString](): string;
+    [Copy](): ObjectField;
 }
 
 // @ts-ignore
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 78143ccda..c946d68e1 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -22,7 +22,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
-    height: number;
+    height?: number;
     display?: string;
     oneLine?: boolean;
 }
@@ -53,6 +53,12 @@ export class EditableView extends React.Component<EditableProps> {
         }
     }
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.editing = true;
+        e.stopPropagation();
+    }
+
     render() {
         if (this.editing) {
             return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
@@ -60,7 +66,7 @@ export class EditableView extends React.Component<EditableProps> {
         } else {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={action(() => this.editing = true)} >
+                    onClick={this.onClick} >
                     <span>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 400562f12..9edbba997 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -29,7 +29,7 @@ import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
 import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { DocServer } from '../DocServer';
 import { listSpec } from '../../new_fields/Schema';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { HistoryUtil } from '../util/History';
 import { CollectionBaseView } from './collections/CollectionBaseView';
 
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 9c37e9000..ce679aa0a 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -7,7 +7,7 @@ import { Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
 import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
-import { Id } from "../../new_fields/RefField";
+import { Id } from "../../new_fields/FieldSymbols";
 import { List } from "../../new_fields/List";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 6e64e1af1..8efd8d266 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -16,7 +16,7 @@ import { isString } from 'util';
 import { constant } from 'async';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { DocumentManager } from '../util/DocumentManager';
 import { SetupDrag } from '../util/DragManager';
 import { Docs } from '../documents/Documents';
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 84ffbac36..5686ccfef 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -7,8 +7,8 @@ import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Typ
 import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
 import { listSpec } from '../../../new_fields/Schema';
 import { List } from '../../../new_fields/List';
-import { Id } from '../../../new_fields/RefField';
 import { SelectionManager } from '../../util/SelectionManager';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 export enum CollectionViewType {
     Invalid,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 483209f86..e904358a9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,7 +6,7 @@ import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
 import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
-import { FieldId, Id } from "../../../new_fields/RefField";
+import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
@@ -21,6 +21,7 @@ import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -75,7 +76,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     @action
-    public CloseRightSplit(document: Doc): boolean {
+    public CloseRightSplit = (document: Doc): boolean => {
         let retVal = false;
         if (this._goldenLayout.root.contentItems[0].isRow) {
             retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
@@ -118,7 +119,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
     @action
-    public AddRightSplit(document: Doc, minimize: boolean = false) {
+    public AddRightSplit = (document: Doc, minimize: boolean = false) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -155,7 +156,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return newContentItem;
     }
     @action
-    public AddTab(stack: any, document: Doc) {
+    public AddTab = (stack: any, document: Doc) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -412,10 +413,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
-    _stack: any;
+    get _stack(): any {
+        return (this.props as any).glContainer.parent.parent;
+    }
     constructor(props: any) {
         super(props);
-        this._stack = (this.props as any).glContainer.parent.parent;
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index a6614da21..5e51437a4 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -8,7 +8,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 import { emptyFunction } from "../../../Utils";
 import { NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f15da41ff..b25b48339 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -23,7 +23,7 @@ import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fie
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { Docs } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 72fa69cb1..8ad495762 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -11,7 +11,7 @@ import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 9ab959f3c..27f23a1a8 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -7,7 +7,7 @@ import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { emptyFunction } from "../../../Utils";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { VideoBox } from "../nodes/VideoBox";
 import { NumCast } from "../../../new_fields/Types";
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 59c763be8..bfdef8e8c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 52f7914f3..4d07c31a7 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -3,7 +3,7 @@ import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
 import { observable, action, runInAction } from "mobx";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { SearchUtil } from "../../util/SearchUtil";
 import { CollectionDockingView } from "./CollectionDockingView";
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index d5ce4e1e7..e1ff715d1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -11,7 +11,7 @@ import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { List } from "../../../../new_fields/List";
-import { Id } from "../../../../new_fields/RefField";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9cb8443f4..ee6f4821f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -22,9 +22,9 @@ import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Sc
 import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
 import { pageSchema } from "../../nodes/ImageBox";
-import { Id } from "../../../../new_fields/RefField";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { HistoryUtil } from "../../../util/History";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 export const panZoomSchema = createSchema({
     panX: "number",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4587c2227..2029b91e5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,9 +17,6 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { ImageField } from "../../../../new_fields/URLField";
 import { Template, Templates } from "../../Templates";
-import { Gateway } from "../../../northstar/manager/Gateway";
-import { DocServer } from "../../../DocServer";
-import { Id } from "../../../../new_fields/RefField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5f218fd1f..87c88f57c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,8 +4,7 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
-import { Copy, ObjectField } from "../../../new_fields/ObjectField";
-import { Id } from "../../../new_fields/RefField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -29,6 +28,7 @@ import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
+import { Id, Copy } from '../../../new_fields/FieldSymbols';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 86437a6c1..8cb576786 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -8,7 +8,7 @@ import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
 import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
-import { Doc, IsField } from "../../../new_fields/Doc";
+import { Doc, Field } from "../../../new_fields/Doc";
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -41,7 +41,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 let res = script.run();
                 if (!res.success) return;
                 const field = res.result;
-                if (IsField(field)) {
+                if (Field.IsField(field)) {
                     realDoc[this._keyInput] = field;
                 }
                 this._keyInput = "";
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 4f7919f50..7a88985c0 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -9,7 +9,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
 import React = require("react");
-import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { Doc, Opt, Field } from '../../../new_fields/Doc';
 import { FieldValue } from '../../../new_fields/Types';
 
 // Represents one row in a key value plane
@@ -75,7 +75,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             let res = script.run();
                             if (!res.success) return false;
                             const field = res.result;
-                            if (IsField(field)) {
+                            if (Field.IsField(field)) {
                                 props.Document[props.fieldKey] = field;
                                 return true;
                             }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 4cf798249..3f09d6214 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -7,7 +7,7 @@ import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 720e1640a..4314e2132 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,11 +3,27 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult } from '../new_fields/Doc';
+import { Doc, Field, FieldResult, Opt } from '../new_fields/Doc';
 import { DocServer } from '../client/DocServer';
-import { Id } from '../new_fields/RefField';
+import { Id } from '../new_fields/FieldSymbols';
 import { List } from '../new_fields/List';
 import { URLField } from '../new_fields/URLField';
+import { EditableView } from '../client/views/EditableView';
+import { CompileScript } from '../client/util/Scripting';
+
+function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean;
+function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean;
+function applyToDoc(doc: any, key: string | number, scriptString: string): boolean {
+    let script = CompileScript(scriptString, { addReturn: true, params: { this: doc instanceof Doc ? Doc.name : List.name } });
+    if (!script.compiled) {
+        return false;
+    }
+    const res = script.run({ this: doc });
+    if (!res.success) return false;
+    if (!Field.IsField(res.result)) return false;
+    doc[key] = res.result;
+    return true;
+}
 
 configure({
     enforceActions: "observed"
@@ -18,12 +34,18 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
     @observable
     expanded = false;
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
             content = (
                 <div>
-                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} />)}
+                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} setValue={value => applyToDoc(this.props.field, index, value)} />)}
                 </div>
             );
         } else {
@@ -31,7 +53,7 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -42,6 +64,13 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
 class DocumentViewer extends React.Component<{ field: Doc }> {
     @observable
     expanded = false;
+
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
@@ -50,7 +79,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
                 return (
                     <div key={key}>
                         <b>({key}): </b>
-                        <DebugViewer field={this.props.field[key]}></DebugViewer>
+                        <DebugViewer field={this.props.field[key]} setValue={value => applyToDoc(this.props.field, key, value)}></DebugViewer>
                     </div>
                 );
             });
@@ -67,7 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -75,7 +104,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
 }
 
 @observer
-class DebugViewer extends React.Component<{ field: FieldResult }> {
+class DebugViewer extends React.Component<{ field: FieldResult, setValue(value: string): boolean }> {
 
     render() {
         let content;
@@ -90,10 +119,14 @@ class DebugViewer extends React.Component<{ field: FieldResult }> {
             content = <p>{field}</p>;
         } else if (field instanceof URLField) {
             content = <p>{field.url.href}</p>;
+        } else if (field instanceof Promise) {
+            return <p>Field loading</p>;
         } else {
-            content = <p>Unrecognized field type</p>;
+            return <p>Unrecognized field type</p>;
         }
-        return content;
+
+        return <EditableView GetValue={() => Field.toScriptString(field)} SetValue={this.props.setValue}
+            contents={content}></EditableView>;
     }
 }
 
@@ -129,7 +162,7 @@ class Viewer extends React.Component {
                     onChange={this.inputOnChange}
                     onKeyDown={this.onKeyPress} />
                 <div>
-                    {this.fields.map((field, index) => <DebugViewer field={field} key={index}></DebugViewer>)}
+                    {this.fields.map((field, index) => <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>)}
                 </div>
             </>
         );
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
index fc144222c..1be1ec3e0 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/new_fields/CursorField.ts
@@ -1,7 +1,8 @@
-import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { observable } from "mobx";
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, createSimpleSchema, object } from "serializr";
+import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols";
 
 export type CursorPosition = {
     x: number,
@@ -52,4 +53,8 @@ export default class CursorField extends ObjectField {
     [Copy]() {
         return new CursorField(this.data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
index c0a79f267..fc8abb9d9 100644
--- a/src/new_fields/DateField.ts
+++ b/src/new_fields/DateField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, date } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("date")
 export class DateField extends ObjectField {
@@ -15,4 +16,8 @@ export class DateField extends ObjectField {
     [Copy]() {
         return new DateField(this.date);
     }
+
+    [ToScriptString]() {
+        return `new DateField(new Date(${this.date.toISOString()}))`;
+    }
 }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 02dd34cb4..f4514c33e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -5,27 +5,33 @@ import { DocServer } from "../client/DocServer";
 import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
 import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
 import { listSpec } from "./Schema";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
-import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
-
-export function IsField(field: any): field is Field {
-    return (typeof field === "string")
-        || (typeof field === "number")
-        || (typeof field === "boolean")
-        || (field instanceof ObjectField)
-        || (field instanceof RefField);
+import { ObjectField } from "./ObjectField";
+import { RefField, FieldId } from "./RefField";
+import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
+
+export namespace Field {
+    export function toScriptString(field: Field): string {
+        if (typeof field === "string") {
+            return `"${field}"`;
+        } else if (typeof field === "number" || typeof field === "boolean") {
+            return String(field);
+        } else {
+            return field[ToScriptString]();
+        }
+    }
+    export function IsField(field: any): field is Field {
+        return (typeof field === "string")
+            || (typeof field === "number")
+            || (typeof field === "boolean")
+            || (field instanceof ObjectField)
+            || (field instanceof RefField);
+    }
 }
 export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
 export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
 
-export const Update = Symbol("Update");
-export const Self = Symbol("Self");
-export const SelfProxy = Symbol("SelfProxy");
-export const WidthSym = Symbol("Width");
-export const HeightSym = Symbol("Height");
-
 /**
  * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.  
  * If a default value is given, that will be returned instead of undefined.  
@@ -43,6 +49,9 @@ export function DocListCast(field: FieldResult): Doc[] {
     return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
 }
 
+export const WidthSym = Symbol("Width");
+export const HeightSym = Symbol("Height");
+
 @Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
     constructor(id?: FieldId, forceSave?: boolean) {
@@ -102,6 +111,10 @@ export class Doc extends RefField {
     public [WidthSym] = () => NumCast(this[SelfProxy].width);  // bcz: is this the right way to access width/height?   it didn't work with : this.width
     public [HeightSym] = () => NumCast(this[SelfProxy].height);
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     public [HandleUpdate](diff: any) {
         console.log(diff);
         const set = diff.$set;
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
new file mode 100644
index 000000000..a436dcf2b
--- /dev/null
+++ b/src/new_fields/FieldSymbols.ts
@@ -0,0 +1,10 @@
+
+export const Update = Symbol("Update");
+export const Self = Symbol("Self");
+export const SelfProxy = Symbol("SelfProxy");
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+export const ToScriptString = Symbol("Copy");
\ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index d998746bb..f952acff9 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("html")
 export class HtmlField extends ObjectField {
@@ -15,4 +16,8 @@ export class HtmlField extends ObjectField {
     [Copy]() {
         return new HtmlField(this.html);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 1a928389d..62b2cd254 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("icon")
 export class IconField extends ObjectField {
@@ -15,4 +16,8 @@ export class IconField extends ObjectField {
     [Copy]() {
         return new IconField(this.icon);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 2d75f8a19..4e3b7abe0 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 import { deepCopy } from "../Utils";
 
 export enum InkTool {
@@ -40,4 +41,8 @@ export class InkField extends ObjectField {
     [Copy]() {
         return new InkField(deepCopy(this.inkData));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 70e36f911..f1e4c4721 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,11 +1,12 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Field, Update, Self, FieldResult, SelfProxy } from "./Doc";
+import { Field } from "./Doc";
 import { setter, getter, deleteProperty, updateFunction } from "./util";
 import { serializable, alias, list } from "serializr";
 import { observable, action } from "mobx";
-import { ObjectField, OnUpdate, Copy, Parent } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols";
 
 const listHandlers: any = {
     /// Mutator methods
@@ -225,7 +226,7 @@ type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
 
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
-    constructor(fields: T[] = []) {
+    constructor(fields?: T[]) {
         super();
         const list = new Proxy<this>(this, {
             set: setter,
@@ -244,7 +245,9 @@ class ListImpl<T extends Field> extends ObjectField {
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
         this[SelfProxy] = list;
-        (list as any).push(...fields);
+        if (fields) {
+            (list as any).push(...fields);
+        }
         return list;
     }
 
@@ -284,6 +287,11 @@ class ListImpl<T extends Field> extends ObjectField {
 
     private [Self] = this;
     private [SelfProxy]: any;
+
+    [ToScriptString]() {
+        return "invalid";
+        // return `new List([${(this as any).map((field => Field.toScriptString(field))}])`;
+    }
 }
 export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
 export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
\ No newline at end of file
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 51768c6db..5f4a6f8fb 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,14 +1,13 @@
 import { Doc } from "./Doc";
 import { RefField } from "./RefField";
-
-export const OnUpdate = Symbol("OnUpdate");
-export const Parent = Symbol("Parent");
-export const Copy = Symbol("Copy");
+import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols";
 
 export abstract class ObjectField {
     protected [OnUpdate](diff?: any) { }
     private [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
+
+    abstract [ToScriptString](): string;
 }
 
 export namespace ObjectField {
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index fd99ae1c0..130ec066e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -3,8 +3,9 @@ import { FieldWaiting } from "./Doc";
 import { primitive, serializable } from "serializr";
 import { observable, action } from "mobx";
 import { DocServer } from "../client/DocServer";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Copy } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
+import { Id, Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("proxy")
 export class ProxyField<T extends RefField> extends ObjectField {
@@ -26,6 +27,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return new ProxyField<T>(this.fieldId);
     }
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     @serializable(primitive())
     readonly fieldId: string = "";
 
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 202c65f21..75ce4287f 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,9 +1,8 @@
 import { serializable, primitive, alias } from "serializr";
 import { Utils } from "../Utils";
+import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
 
 export type FieldId = string;
-export const HandleUpdate = Symbol("HandleUpdate");
-export const Id = Symbol("Id");
 export abstract class RefField {
     @serializable(alias("id", primitive()))
     private __id: FieldId;
@@ -15,4 +14,6 @@ export abstract class RefField {
     }
 
     protected [HandleUpdate]?(diff: any): void;
+
+    abstract [ToScriptString](): string;
 }
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index eb30e76de..89d077a47 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -1,6 +1,7 @@
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { serializable } from "serializr";
 import { Deserializable } from "../client/util/SerializationHelper";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
@@ -15,4 +16,8 @@ export class RichTextField extends ObjectField {
     [Copy]() {
         return new RichTextField(this.Data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index d00a95a16..a6f8f1cc5 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { ToScriptString, Copy } from "./FieldSymbols";
 
 function url() {
     return custom(
@@ -13,7 +14,7 @@ function url() {
     );
 }
 
-export class URLField extends ObjectField {
+export abstract class URLField extends ObjectField {
     @serializable(url())
     readonly url: URL;
 
@@ -22,6 +23,10 @@ export class URLField extends ObjectField {
         this.url = url;
     }
 
+    [ToScriptString]() {
+        return `new ${this.constructor.name}(new URL(${this.url.href}))`;
+    }
+
     [Copy](): this {
         return new (this.constructor as any)(this.url);
     }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index d94994a07..65a37a0d1 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,11 +1,12 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Update, Doc, Field } from "./Doc";
+import { Doc, Field } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField } from "./Proxy";
 import { FieldValue } from "./Types";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
 import { action } from "mobx";
+import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
 
 export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
     if (SerializationHelper.IsSerializing()) {
-- 
cgit v1.2.3-70-g09d2


From 890a40ef8cc2efa7beea5722301b59ede5141238 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 30 May 2019 13:47:53 -0400
Subject: fixed context menus and tree view jitter.

---
 src/client/util/DragManager.ts                     |  4 +--
 src/client/util/TooltipTextMenu.tsx                |  2 +-
 src/client/views/ContextMenu.scss                  |  2 +-
 src/client/views/ContextMenuItem.tsx               |  2 +-
 src/client/views/MainView.tsx                      |  6 ++--
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionTreeView.scss      |  6 +++-
 .../views/collections/CollectionTreeView.tsx       | 33 +++++++++++++---------
 src/client/views/collections/CollectionView.tsx    | 30 +++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 31 ++++++++++++++------
 src/client/views/nodes/FormattedTextBox.tsx        | 25 +---------------
 src/client/views/nodes/ImageBox.tsx                | 11 ++++----
 .../authentication/models/current_user_utils.ts    | 20 ++++++-------
 13 files changed, 85 insertions(+), 93 deletions(-)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7f75a95f0..8f0cce095 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -26,7 +26,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
             } else {
                 document.addEventListener("pointermove", onRowMove);
@@ -264,7 +264,7 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a1f80120f..5dd10f1bf 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -194,7 +194,7 @@ export class TooltipTextMenu {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                                 }
-                                else CollectionDockingView.Instance.AddRightSplit(f);
+                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
                             }
                         }));
                     }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 61ae69179..74acba615 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -22,7 +22,7 @@
     color: $light-color;
 }
 
-.subMenu-cont {
+.contextMenu-subMenu-cont {
     position: absolute;
     display: flex;
     z-index: 1000;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 63347e076..fcda0db89 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -56,7 +56,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
         else {
             let submenu = !this.overItem ? (null) :
-                <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             return (
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 74dafadc4..a093ffdec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -104,7 +104,9 @@ export class MainView extends React.Component {
         }, false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", action(function (e: PointerEvent) {
-            if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+
+            const targets = document.elementsFromPoint(e.x, e.y);
+            if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
                 ContextMenu.Instance.clearItems();
             }
         }), true);
@@ -163,7 +165,7 @@ export class MainView extends React.Component {
     }
 
     openNotifsCol = () => {
-        if (this._notifsCol) {
+        if (this._notifsCol && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b6076b5f7..dcc1bd95d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -22,6 +22,7 @@ import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
 import { Id } from '../../../new_fields/FieldSymbols';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -415,7 +416,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
     get _stack(): any {
-        return (this.props as any).glContainer.parent.parent;
+        let parent = (this.props as any).glContainer.parent.parent;
+        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+            return parent.parent.contentItems[1];
+        return parent;
     }
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2f0329fc4..bb3be0a73 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -48,7 +48,11 @@
         .treeViewItem-openRight {
             display: inline-block;
             // display: inline;
-            transform: translate(0px, -3px); //TODO Fix this
+            svg {
+                display:block;
+                padding:0px;
+                margin: 0px;
+            }
         }
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 85aab96d3..2814c0502 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
+import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
@@ -19,6 +19,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Docs } from '../../documents/Documents';
 import { MainView } from '../MainView';
+import { CollectionViewType } from './CollectionBaseView';
 
 
 export interface TreeViewProps {
@@ -54,7 +55,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (this.props.document.dockingConfig) {
             MainView.Instance.openWorkspace(this.props.document);
         } else {
-            CollectionDockingView.Instance.AddRightSplit(this.props.document);
+            this.props.addDocTab(this.props.document, "openRight");
         }
     }
 
@@ -122,11 +123,11 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
-        let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+        let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
         let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
             <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
-                <FontAwesomeIcon icon="angle-right" size="lg" />
+                {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */}
             </div>);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
@@ -139,14 +140,19 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight") });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight") });
-            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
-                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+            if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
+                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
+                if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+                }
+                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            } else {
+                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -207,11 +213,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
+        if (!e.isPropagationStopped() && this.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document
             ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
-        }
-        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
+            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
         }
     }
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a1e7c7c69..68eefab4c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,20 +1,19 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
 import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
-import { NumCast } from '../../../new_fields/Types';
-import { WidthSym, HeightSym } from '../../../new_fields/Doc';
+import { CollectionTreeView } from "./CollectionTreeView";
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -44,26 +43,17 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
+            let subItems: ContextMenuProps[] = [];
+            subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
             if (CollectionBaseView.InSafeMode()) {
                 ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
             }
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
-            ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
+            subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
+            subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
+            subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+            ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a16a52ac6..69691b6f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,6 +29,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
+import { ContextMenuProps } from '../ContextMenuItem';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
@@ -263,7 +264,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                             expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
                         }
                     }
-                    if (maxLocation && maxLocation !== "inPlace") {
+                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
@@ -295,7 +296,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
-        if (e.shiftKey && e.buttons === 1) {
+        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
         } else {
@@ -341,7 +342,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (): void => {
-        CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
@@ -398,6 +399,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.templates = this.templates;
     }
 
+    freezeNativeDimensions = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.proto!.nativeWidth = undefined;
+            this.props.Document.proto!.nativeHeight = undefined;
+
+        } else {
+            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
+
     @action
     onContextMenu = (e: React.MouseEvent): void => {
         e.stopPropagation();
@@ -409,16 +421,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.preventDefault();
 
         const cm = ContextMenu.Instance;
-        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
-        cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
-        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+        let subitems: ContextMenuProps[] = [];
+        subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
+        subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
+        cm.addItem({ description: "Open...", subitems: subitems });
+        cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
+                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index da584c811..5d93edaac 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -240,7 +240,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                             if (DocumentManager.Instance.getDocumentView(f)) {
                                 DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                             } else {
-                                CollectionDockingView.Instance.AddRightSplit(f);
+                                this.props.addDocTab && this.props.addDocTab(f, "onRight");
                             }
                         }
                     }));
@@ -274,29 +274,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
     }
-
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        if (!this._gotDown) {
-            e.preventDefault();
-            return;
-        }
-        ContextMenu.Instance.addItem({
-            description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
-            event: this.freezeNativeDimensions,
-            icon: "edit"
-        });
-    }
-
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.props.isSelected()) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ea6c5436..4c2b73b70 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -19,6 +19,7 @@ import { InkingControl } from '../InkingControl';
 import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
+import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenuItem';
 var path = require('path');
 
 
@@ -131,12 +132,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let field = Cast(this.Document[this.props.fieldKey], ImageField);
         if (field) {
             let url = field.url.href;
-            ContextMenu.Instance.addItem({
-                description: "Copy path", event: () => {
-                    Utils.CopyText(url);
-                }, icon: "expand-arrows-alt"
-            });
-            ContextMenu.Instance.addItem({
+            let subitems: ContextMenuProps[] = [];
+            subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" });
+            subitems.push({
                 description: "Rotate", event: action(() => {
                     this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
                     let nw = this.props.Document.nativeWidth;
@@ -147,6 +145,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     this.props.Document.height = w;
                 }), icon: "expand-arrows-alt"
             });
+            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: subitems });
         }
     }
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index add347a88..e5b7a025b 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,21 +1,17 @@
-import { computed, observable, action, runInAction } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import * as rp from 'request-promise';
+import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
-import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
+import { Gateway, NorthstarSettings } from "../../../client/northstar/manager/Gateway";
+import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
-import { RouteStore } from "../../RouteStore";
-import { DocServer } from "../../../client/DocServer";
-import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
-import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
-import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
-import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
-import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
-import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
+import { Cast } from "../../../new_fields/Types";
+import { RouteStore } from "../../RouteStore";
 
 export class CurrentUserUtils {
     private static curr_email: string;
-- 
cgit v1.2.3-70-g09d2


From e83c2a88303ae6d994a0ac2e84214947cac4d96d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 31 May 2019 00:44:30 -0400
Subject: Added Open Tab Alias and Open Right Alias to context menu Cleaned up
 MakeAlias

---
 src/client/util/TooltipTextMenu.tsx     |  1 +
 src/client/views/nodes/DocumentView.tsx |  2 ++
 src/new_fields/Doc.ts                   | 13 ++-----------
 3 files changed, 5 insertions(+), 11 deletions(-)

(limited to 'src/client/util/TooltipTextMenu.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 5dd10f1bf..f517f757a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -198,6 +198,7 @@ export class TooltipTextMenu {
                             }
                         }));
                     }
+                    // TODO This should have an else to handle external links
                     e.stopPropagation();
                     e.preventDefault();
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7c142c3b..4905f0ddc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -424,7 +424,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let subitems: ContextMenuProps[] = [];
         subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
         subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "inTab"), icon: "folder" });
         subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems });
         cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0c74b8f65..7f7263cf1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -213,19 +213,10 @@ export namespace Doc {
 
 
     export function MakeAlias(doc: Doc) {
-        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : undefined;
         const alias = new Doc;
-
-        if (!proto) {
-            alias.proto = doc;
-        } else {
-            PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-                if (proto) {
-                    alias.proto = proto;
-                }
-            });
+        if (!GetT(doc, "isPrototype", "boolean", true)) {
+            alias.proto = doc.proto;
         }
-
         return alias;
     }
 
-- 
cgit v1.2.3-70-g09d2

